Doporučené postupy a příklady (poznámky SAL)
Tady je několik způsobů, jak z jazyka SAL (Source Code Annotation Language) získat co nejvíce a vyhnout se některým běžným problémům.
_In_
Pokud má funkce zapisovat do elementu , použijte _Inout_ místo _In_ . To je zvláště důležité v případech automatizovaného převodu ze starších maker na SAL. Před SAL mnoho programátorů používalo makra jako komentáře – makra s názvem IN , OUT , nebo IN_OUT varianty těchto názvů. Přestože doporučujeme tato makra převést na SAL, doporučujeme vám být při převodu opatrní, protože kód se mohl od napsání původního prototypu změnit a staré makro už nemusí odrážet, co kód dělá. Buďte obzvláště opatrní u makra komentáře, protože je často nesprávně umístěno – například na nesprávné straně OPTIONAL čárky.
// 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_
Pokud volajícímu není povoleno předat ukazatel s hodnotou null, použijte místo nebo _In__Out_ nebo _In_opt__Out_opt_ . To platí i pro funkci, která kontroluje její parametry a vrací chybu, pokud má hodnotu NULL, pokud by neměla. Přestože je vhodné defenzivní kódování zkontrolovat parametr funkce na neočekávanou hodnotu NULL a vrátit se bez výpadku, neznamená to, že anotace parametru může být volitelného typu ( _*Xxx*_opt_ ).
// Incorrect
void Func1(_Out_opt_ int *p1)
{
*p = 1;
}
// Correct
void Func2(_Out_ int *p1)
{
*p = 1;
}
_Pre_defensive_ a _Post_defensive_
Pokud se funkce zobrazí na hranici vztahu důvěryhodnosti, doporučujeme použít _Pre_defensive_ poznámku . "Obranný" modifikátor upravuje určité poznámky, aby indikoval, že v okamžiku volání by mělo být rozhraní kontrolováno striktně, ale v těle implementace by mělo předpokládat, že mohou být předány nesprávné parametry. V takovém případě se upřednostní na hranici vztahu důvěryhodnosti, aby indikoval, že přestože se volajícímu při pokusu o předání hodnoty NULL zobrazí chyba, tělo funkce bude analyzováno, jako by parametr mohl mít hodnotu NULL, a všechny pokusy o odkazování na ukazatel bez předchozí kontroly hodnoty NULL budou _In_ _Pre_defensive_ označeny příznakem. K dispozici je také anotace, která se používá ve zpětných voláních, kde se předpokládá, že důvěryhodná strana je volající a nedůvěryhodný kód _Post_defensive_ je zvaný kód.
_Out_writes_
Následující příklad ukazuje běžné zneužití _Out_writes_ .
// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
DWORD size
);
Poznámka označuje, _Out_writes_ že máte vyrovnávací paměť. cbPřidělí se bajty, první bajt se inicializuje při ukončení. Tato poznámka není striktně chybná a je užitečné vyjádřit přidělenou velikost. Neukadá ale, kolik prvků funkce inicializovala.
Další příklad ukazuje tři správné způsoby úplného určení přesné velikosti inicializované části vyrovnávací paměti.
// 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
Použití je _Out_ PSTR téměř vždy nesprávné. To je interpretováno jako parametr výstupu, který odkazuje na vyrovnávací paměť znaků a je ukončen hodnotou NULL.
// Incorrect
void Func1(_Out_ PSTR pFileName, size_t n);
// Correct
void Func2(_Out_writes_(n) PSTR wszFileName, size_t n);
Anotace, jako _In_ PCSTR je , je běžná a užitečná. Odkazuje na vstupní řetězec, který má ukončení null, protože předběžná podmínka umožňuje rozpoznání řetězce _In_ ukončené hodnotou NULL.
_In_ WCHAR* p
_In_ WCHAR* p říká, že existuje vstupní ukazatel, p který odkazuje na jeden znak. Ve většině případů to ale pravděpodobně není zamýšlená specifikace. Místo toho je pravděpodobně zamýšlena specifikace pole ukončeného hodnotou NULL. K tomu použijte _In_ PWSTR .
// Incorrect
void Func1(_In_ WCHAR* wszFileName);
// Correct
void Func2(_In_ PWSTR wszFileName);
Běžná je chybějící správná specifikace ukončení hodnoty NULL. Pomocí příslušné STR verze nahraďte typ , jak je znázorněno v následujícím příkladu.
// 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_
Pokud je parametr ukazatelem a chcete vyjádřit rozsah hodnoty prvku, na který ukazatel odkazuje, použijte _Deref_out_range_ místo _Out_range_ . V následujícím příkladu se vyjádří rozsah *fillFilled, nikoli funkcefillfilled.
// 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) není pro některé nástroje striktně vyžadován, protože je možné ho odvodit z , ale je zde uveden _Out_writes_to_(cbSize,*pcbFilled) pro úplnost.
Nesprávný kontext v _When_
Další běžnou chybou je použití vyhodnocení po stavu pro předběžné podmínky. V následujícím příkladu _Requires_lock_held_ je předběžná podmínka.
// 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);
Výraz result odkazuje na hodnotu po stavu, která není k dispozici v předběžném stavu.
TRUE v _Success_
Pokud je funkce úspěšná, pokud je vrácená hodnota nenulová, použijte jako podmínku úspěchu return != 0 místo return == TRUE . Nenulová hodnota nemusí nutně znamenat ekvivalenci skutečné hodnotě, kterou kompilátor poskytuje pro TRUE . Parametr parametru je výraz a následující výrazy jsou vyhodnoceny jako ekvivalentní: , , a bez _Success_return != 0 parametrů nebo return != falsereturn != FALSEreturn porovnání.
// 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
);
Referenční proměnná
Pro referenční proměnnou předchozí verze SAL použila implicitní ukazatel jako cíl poznámky a vyžadovala přidání do poznámek připojených k __deref referenční proměnné. Tato verze používá samotný objekt a nevyžaduje další _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
);
Poznámky k vrácených hodnotám
Následující příklad ukazuje běžný problém s poznámkami k návratové hodnotě.
// Incorrect
_Out_opt_ void *MightReturnNullPtr1();
// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();
V tomto _Out_opt_ příkladu se říká, že ukazatel může být v rámci předběžné podmínky null. Na návratovou hodnotu však nelze použít předběžné podmínky. V tomto případě je správná poznámka _Ret_maybenull_ .
Viz také
Použití poznámek SAL k snížení míry výskytu závad kódu C/C++
Porozumění SAL
Zadávání poznámek k parametrům funkcí a návratovým hodnotám
Zadávání poznámek k chování funkcí
Zadávání poznámek ke strukturám a třídám
Zadávání poznámek o chování při zamykání
Určení, kdy a kde se má poznámka použít
Vnitřní funkce