Udostępnij za pośrednictwem


Najlepsze praktyki i przykłady (SAL)

Oto kilka sposobów na wykorzystanie Source Code Annotation Language (SAL) w pełni i uniknięcie typowych problemów.

_In_

Jeśli funkcja powinna zapisywać do elementu, użyj _Inout_ zamiast _In_.Jest to szczególnie istotne w przypadku zautomatyzowanych konwersji ze starszych makr do SAL.Przed SAL, wielu programistów używało makr jako komentarzy — makra, które zostały nazwane IN, OUT, IN_OUT, lub wariantami tych nazw.Chociaż zaleca się, aby przekonwertować te makra do SAL, również radzimy zachować ostrożność podczas ich konwertowania, ponieważ kod może być zmieniony, jako że oryginalny prototyp został napisany i stare makro może nie odzwierciedlać tego, co robi kod.Należy zachować szczególną ostrożność z makrem komentarza OPTIONAL, ponieważ często jest nieprawidłowo umieszczony — na przykład po złej stronie przecinka.

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

    *p1 = 1;
}

// Correct
void Func2(_Inout_ PCHAR p1)
{
    if (p1 == NULL) 
        return;

    *p1 = 1;
}

_opt_

Jeśli wywołujący nie może przekazać wskaźnika null, użyj _In_ lub _Out_ zamiast _In_opt_ lub _Out_opt_.Dotyczy to nawet funkcji, która sprawdza swoje parametry i zwraca błąd, jeśli ma wartość NULL, kiedy jej nie powinno być.Chociaż sprawdzanie w funkcji jej parametrów czy nie ma nieoczekiwanej wartości NULL i bezpieczny powrót jest dobrą, obronną praktyką kodowania, nie oznacza że adnotacja parametru może być opcjonalnego typu (_Xxx_opt_).

// 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 pojawi się w granicach zaufania, zaleca się używanie _Pre_defensive_ adnotacji.Modyfikator "defensive" modyfikuje pewne adnotacje, aby wskazać, że w punkcie wywołania, interfejs powinien być ściśle sprawdzany, ale w treści implementacji powinien założyć, że mogą być przekazywane nieprawidłowe parametry.W takim przypadku _In_ _Pre_defensive_ jest preferowany w granic zaufania, aby wskazać, że chociaż rozmówca zostanie wyświetlony błąd, jeśli podejmowana jest próba przekazać NULL, treści funkcji będą analizowane tak, jakby parametr może mieć wartości NULL, a wszystkie próby odniesienia do pustego wskaźnika bez uprzedniego sprawdzenia pod kątem wartości NULL zostaną oflagowane.A _Post_defensive_ adnotacji jest również dostępne do użytku w wywołania zwrotne, gdzie zaufanej strony zakłada się, obiekt wywołujący i niezaufanym kodem jest zwany kodem.

_Out_writes_

Poniższy przykład ilustruje typowe, niewłaściwe użycie _Out_writes_.

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

Adnotacja _Out_writes_ oznacza, że posiadasz bufor.Ma on cb przydzielonych bajtów z pierwszym bajtem zainicjowanym na wyjściu.Adnotacja nie jest ściśle niewłaściwa i jest pomocna do wyrażenia rozmiaru przydziału.Jednakże, nie mówi ile elementów jest zainicjowanych przez funkcję.

W kolejnym przykładzie przedstawiono trzy poprawne sposoby aby w pełni określić dokładny rozmiar zainicjowanej części buforu.

// 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

Użycie _Out_ PSTR prawie zawsze jest niewłaściwe.To jest interpretowane jako parametr wyjściowy, który wskazuje bufor znaków i jest on zakończony znakiem NULL.

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

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

Adnotację jak _In_ PCSTR jest typowa i użyteczna.Wskazuje na ciąg wejściowy, który ma zakończenie NULL, ponieważ warunek wstępny _In_ pozwala na uznanie ciągu zakończonego wartością NULL.

_In_ WCHAR* p

_In_ WCHAR* p mówi, że jest wejściowy wskaźnik p, który wskazuje na jeden znak.Jednak w większości przypadków, prawdopodobnie nie jest to zamierzona specyfikacja.Zamiast tego, to co jest prawdopodobnie zamierzone, to specyfikacja tablicy zakończonej wartością NULL; aby to zrobić, użyj _In_ PWSTR.

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

// Correct
void Func2(_In_ PWSTR wszFileName);

Brak prawidłowej specyfikacji zakończenia wartością NULL jest typowe.Użyj odpowiedniej wersji STR, aby zastąpić typ tak, jak pokazano w następującym przykładzie.

// 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, który jest wskazywany przez wskaźnik, użyj _Deref_out_range_ zamiast _Out_range_.W poniższym przykładzie zakres *pcbFilled jest wyrażony, nie pcbFilled.

// 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 bezwzględnie wymagane dla niektórych narzędzi, ponieważ można go wywnioskować z _Out_writes_to_(cbSize,*pcbFilled), ale jest tutaj pokazany dla kompletności.

Nieprawidłowy kontekst w _When_

Inny typowy błąd to użycie szacowanie post-state dla warunków wstępnych.W poniższym przykładzie _Requires_lock_held_ jest warunkiem wstępnym.

// 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 result odnosi się do wartości post-state, która nie jest dostępna w pre-state.

Wartość TRUE w _Success_

Jeśli funkcja powodzi się, gdy zwracana jest wartość niezerowa, użyj return != 0 jako warunek sukcesu zamiast return == TRUE.Nie zero niekoniecznie oznacza równoważność z rzeczywistą wartością, która dostarcza kompilator dla TRUE.Parametr do _Success_ jest wyrażenie, a poniższe wyrażenia są oceniane jako odpowiednik: return != 0, return != false, return != FALSE, i return bez parametrów i 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 odwołania

Dla zmiennej odwołania, poprzednia wersja SAL używała wskaźnika jako celu adnotacji i wymagała dodania __deref do adnotacji, które dołączały do zmiennej odniesienia.Ta wersja używa samego obiektu i nie wymaga dodatkowego _Deref_.

// 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 do Wartości zwróconych

Poniższy przykład przedstawia typowy problem w adnotacjach wartości zwracanej.

// Incorrect
_Out_opt_ void *MightReturnNullPtr1();

// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();

W tym przykładzie, _Out_opt_ mówi, że wskaźnik może być NULL jako część warunku wstępnego.Jednakże, warunki wstępne nie mogą być zastosowane do wartości zwracanej.W tym przypadku, poprawna adnotacja to _Ret_maybenull_.

Zobacz też

Informacje

Dodawanie adnotacji do parametrów funkcji i zwracanych wartości

Zachowanie funkcji dodawania adnotacji

Dodawanie adnotacji struktur i klas

Dodawanie adnotacji do zachowania blokującego

Określanie warunków pojawiania się adnotacji

Funkcje wewnętrzne

Koncepcje

Zrozumienie SAL

Inne zasoby

Za pomocą adnotacji SAL do zmniejszenia wady kod C/C++