Najlepsze praktyki i przykłady (SAL)

Poniżej przedstawiono kilka sposobów, aby jak najlepiej wykorzystać język adnotacji kodu źródłowego (SAL) i uniknąć niektórych typowych problemów.

_In_

Jeśli funkcja ma zapisywać w elemecie , użyj polecenia _Inout_ zamiast _In_. Jest to istotne w przypadku automatycznej konwersji ze starszych makr do SAL. Przed SAL wielu programistów używa makr jako komentarzy — makr o nazwie IN, OUT, IN_OUTlub wariantów tych nazw. Mimo że zalecamy przekonwertowanie tych makr na SAL, zalecamy również zachowanie ostrożności podczas ich konwersji, ponieważ kod mógł ulec zmianie od czasu zapisania oryginalnego prototypu, a stare makro może już nie odzwierciedlać tego, co robi kod. Należy szczególnie uważać na OPTIONAL makro komentarza, ponieważ jest on często umieszczany niepoprawnie — na przykład po niewłaściwej stronie przecinka.

#include <sal.h>

// Incorrect
void Func1(_In_ int *p1)
{
    if (p1 == NULL)
        return;

    *p1 = 1;
}

// Correct
// _Out_opt_ because the function tolerates NULL as a valid argument, i.e.
// no error is returned. If the function didn't check p1 for NULL, then
// _Out_ would be the better choice
void Func2(_Out_opt_ PCHAR p1)
{
    if (p1 == NULL)
        return;

    *p1 = 1;
}

_opt_

Jeśli obiekt wywołujący nie może przekazać wskaźnika o wartości null, użyj polecenia _In_ lub _Out_ zamiast _In_opt_ lub _Out_opt_. Dotyczy to nawet funkcji, która sprawdza jego parametry i zwraca błąd, jeśli NULL nie powinien być. Mimo że funkcja sprawdza swój parametr pod kątem nieoczekiwanych NULL i zwracanych w sposób prawidłowy jest dobrym rozwiązaniem w zakresie kodowania defensywnego, nie oznacza to, że adnotacja parametru może być typu opcjonalnego (_*Xxx*_opt_).

#include <sal.h>

// Incorrect
void Func1(_Out_opt_ int *p1)
{
    *p = 1;
}

// Correct
void Func2(_Out_ int *p1)
{
    *p = 1;
}

_Pre_defensive_ i _Post_defensive_

Jeśli funkcja jest wyświetlana na granicy zaufania, zalecamy użycie adnotacji _Pre_defensive_ . Modyfikator "defensywny" modyfikuje niektóre adnotacje, aby wskazać, że w momencie wywołania interfejs powinien być ściśle sprawdzany, ale w treści implementacji należy założyć, że mogą zostać przekazane nieprawidłowe parametry. W takim przypadku preferowana jest granica zaufania, aby wskazać, _In_ _Pre_defensive_ że chociaż obiekt wywołujący otrzymuje błąd, jeśli próbuje przekazać NULLwartość , treść funkcji jest analizowana tak, jakby parametr mógł mieć NULLwartość , a wszelkie próby wyłudzenia wskaźnika bez uprzedniego sprawdzenia, czy element jest NULL oflagowany. Adnotacja _Post_defensive_ jest również dostępna do użycia w wywołaniach zwrotnych, w których przyjmuje się, że zaufana strona jest obiektem wywołującym, a niezaufany kod jest nazywany kodem.

_Out_writes_

W poniższym przykładzie pokazano typowe nieprawidłowe użycie obiektu _Out_writes_.

#include <sal.h>

// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
    DWORD size
);

Adnotacja _Out_writes_ oznacza, że masz bufor. cb Ma przydzielone bajty z pierwszym bajtem zainicjowanym po zakończeniu. Ta adnotacja nie jest ściśle nieprawidłowa i warto wyrazić przydzielony rozmiar. Nie określa jednak, ile elementów inicjuje funkcja.

W następnym przykładzie przedstawiono trzy poprawne sposoby pełnego określenia dokładnego rozmiaru zainicjowanej części buforu.

#include <sal.h>

// Correct
void Func1(_Out_writes_to_(size, *pCount) CHAR *pb,
    DWORD size,
    PDWORD pCount
);

void Func2(_Out_writes_all_(size) CHAR *pb,
    DWORD size
);

void Func3(_Out_writes_(size) PSTR pb,
    DWORD size
);

_Out_ PSTR

Korzystanie z _Out_ PSTR programu jest prawie zawsze błędne. Ta kombinacja jest interpretowana jako parametr wyjściowy wskazujący bufor znaków, a bufor jest zakończony wartością null.

#include <sal.h>

// Incorrect
void Func1(_Out_ PSTR pFileName, size_t n);

// Correct
void Func2(_Out_writes_(n) PSTR wszFileName, size_t n);

Adnotacja, podobna _In_ PCSTR do tego, jest powszechna i przydatna. Wskazuje on ciąg wejściowy, który ma zakończenie o wartości null, ponieważ warunek wstępny _In_ umożliwia rozpoznawanie ciągu zakończonego wartością null.

_In_ WCHAR* p

_In_ WCHAR* p mówi, że istnieje wskaźnik p wejściowy wskazujący jeden znak. Jednak w większości przypadków nie jest to specyfikacja, która jest przeznaczona. Zamiast tego, co jest prawdopodobnie zamierzone, jest specyfikacją tablicy zakończonej wartością null; w tym celu użyj polecenia _In_ PWSTR.

#include <sal.h>

// Incorrect
void Func1(_In_ WCHAR* wszFileName);

// Correct
void Func2(_In_ PWSTR wszFileName);

Brak prawidłowej specyfikacji zakończenia wartości null jest powszechny. Użyj odpowiedniej STR wersji, aby zastąpić typ, jak pokazano w poniższym przykładzie.

#include <sal.h>
#include <string.h>

// Incorrect
BOOL StrEquals1(_In_ PCHAR p1, _In_ PCHAR p2)
{
    return strcmp(p1, p2) == 0;
}

// Correct
BOOL StrEquals2(_In_ PSTR p1, _In_ PSTR p2)
{
    return strcmp(p1, p2) == 0;
}

_Out_range_

Jeśli parametr jest wskaźnikiem i chcesz wyrazić zakres wartości elementu wskazywanego przez wskaźnik, użyj _Deref_out_range_ zamiast _Out_range_. W poniższym przykładzie zakres *pcbFilled jest wyrażony, a nie pcbFilled.

#include <sal.h>

// Incorrect
void Func1(
    _Out_writes_bytes_to_(cbSize, *pcbFilled) BYTE *pb,
    DWORD cbSize,
    _Out_range_(0, cbSize) DWORD *pcbFilled
);

// Correct
void Func2(
    _Out_writes_bytes_to_(cbSize, *pcbFilled) BYTE *pb,
    DWORD cbSize,
    _Deref_out_range_(0, cbSize) _Out_ DWORD *pcbFilled
);

_Deref_out_range_(0, cbSize) nie jest ściśle wymagany w przypadku niektórych narzędzi, ponieważ można go wywnioskować z _Out_writes_to_(cbSize,*pcbFilled)elementu , ale jest on pokazany tutaj pod kątem kompletności.

Niewłaściwy kontekst w _When_

Innym typowym błędem jest użycie oceny po stanie dla warunków wstępnych. W poniższym przykładzie _Requires_lock_held_ jest warunkiem wstępnym.

#include <sal.h>

// Incorrect
_When_(return == 0, _Requires_lock_held_(p->cs))
int Func1(_In_ MyData *p, int flag);

// Correct
_When_(flag == 0, _Requires_lock_held_(p->cs))
int Func2(_In_ MyData *p, int flag);

Wyrażenie return odwołuje się do wartości po stanie, która nie jest dostępna w stanie wstępnym.

TRUE w systemie _Success_

Jeśli funkcja powiedzie się, gdy wartość zwracana jest niezerowa, użyj return != 0 jako warunku powodzenia zamiast return == TRUE. Nonzero nie musi oznaczać równoważności rzeczywistej wartości, którą kompilator udostępnia dla TRUEelementu . Parametr to _Success_ wyrażenie, a następujące wyrażenia są oceniane jako równoważne: return != 0, return != false, return != FALSEi return bez parametrów ani porównań.

// Incorrect
_Success_(return == TRUE) _Acquires_lock_(*lpCriticalSection)
BOOL WINAPI TryEnterCriticalSection(
  _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

// Correct
_Success_(return != 0) _Acquires_lock_(*lpCriticalSection)
BOOL WINAPI TryEnterCriticalSection(
  _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

Zmienna referencyjna

W przypadku zmiennej referencyjnej poprzednia wersja SAL użyła dorozumianego wskaźnika jako elementu docelowego adnotacji i wymagała dodania __deref adnotacji do adnotacji dołączonych do zmiennej referencyjnej. Ta wersja używa samego obiektu i nie wymaga _Deref_elementu .

#include <sal.h>

// Incorrect
void Func1(
    _Out_writes_bytes_all_(cbSize) BYTE *pb,
    _Deref_ _Out_range_(0, 2) _Out_ DWORD &cbSize
);

// Correct
void Func2(
    _Out_writes_bytes_all_(cbSize) BYTE *pb,
    _Out_range_(0, 2) _Out_ DWORD &cbSize
);

Adnotacje dotyczące zwracanych wartości

W poniższym przykładzie przedstawiono typowy problem z adnotacjami wartości zwracanych.

#include <sal.h>

// Incorrect
_Out_opt_ void *MightReturnNullPtr1();

// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();

W tym przykładzie mówi, _Out_opt_ że wskaźnik może być NULL częścią warunku wstępnego. Nie można jednak zastosować warunków wstępnych do wartości zwracanej. W tym przypadku poprawną adnotacją jest _Ret_maybenull_.

Zobacz też

Używanie adnotacji SAL w celu zmniejszenia liczby wad kodu C/C++
Informacje o języku SAL
Dodawanie adnotacji do parametrów funkcji i zwracanych wartości
Dodawanie adnotacji do zachowania funkcji
Dodawanie adnotacji do struktur i klas
Dodawanie adnotacji do zachowania blokowania
Określanie, kiedy i gdzie ma zastosowanie adnotacja
Funkcje wewnętrzne