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