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_OUT
lub 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ć NULL
wartość , treść funkcji jest analizowana tak, jakby parametr mógł mieć NULL
wartość , 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 TRUE
elementu . Parametr to _Success_
wyrażenie, a następujące wyrażenia są oceniane jako równoważne: return != 0
, return != false
, return != FALSE
i 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