Osvědčené postupy a příklady (SAL)
Tady je několik způsobů, jak získat maximum z jazyka SAL (Source Code Annotation Language) a vyhnout se některým běžným problémům.
_In_
Pokud má funkce psát do elementu, použijte _Inout_
místo _In_
. To je relevantní v případech automatizovaného převodu ze starších maker na sal. Před sal, mnoho programátorů použilo makra jako komentáře – makra s názvem IN
, OUT
, IN_OUT
nebo varianty těchto názvů. I když doporučujeme převést tato makra na SAL, doporučujeme, abyste byli 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á. Dávejte pozor hlavně na OPTIONAL
makro komentáře, protože se často nesprávně umísťuje – například na špatnou stranu čárky.
#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_
Pokud volajícímu není povoleno předávat ukazatel null, použijte _In_
nebo _Out_
místo _In_opt_
nebo _Out_opt_
. To platí i pro funkci, která kontroluje její parametry a vrací chybu, pokud je NULL
tam, kde by neměla být. I když má funkce kontrolu jeho parametru neočekávaný NULL
a bezproblémový návrat je dobrým obranném programováním, neznamená to, že poznámka k parametru může být volitelného typu (_*Xxx*_opt_
).
#include <sal.h>
// 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 důvěryhodnosti, doporučujeme použít poznámku _Pre_defensive_
. Modifikátor "obranného" upravuje určité poznámky tak, aby značily, že v okamžiku volání by mělo být rozhraní kontrolováno přísně, ale v těle implementace by se mělo předpokládat, že mohou být předány nesprávné parametry. V takovém případě je upřednostňovaná hranice důvěryhodnosti, která označuje, _In_ _Pre_defensive_
že i když volající obdrží chybu, pokud se pokusí předat NULL
, tělo funkce je analyzováno, jako by parametr mohl být NULL
, a všechny pokusy o dereference ukazatele bez první kontroly NULL
, že jsou označeny příznakem. K _Post_defensive_
dispozici je také poznámka, která se používá v zpětných voláních, kde se předpokládá, že důvěryhodná strana je volajícím a nedůvěryhodný kód je volaný kód.
_Out_writes_
Následující příklad ukazuje běžné zneužití _Out_writes_
.
#include <sal.h>
// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
DWORD size
);
Poznámka _Out_writes_
označuje, že máte vyrovnávací paměť. cb
Má přidělené bajty s prvním bajtem inicializovaným při ukončení. Tato poznámka není přísně špatná a je užitečné vyjádřit přidělenou velikost. Neřekne ale, kolik prvků funkce inicializuje.
Následující příklad ukazuje tři správné způsoby, jak plně určit přesnou velikost inicializované části vyrovnávací paměti.
#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
Použití _Out_ PSTR
je téměř vždy špatné. Tato kombinace se interpretuje jako výstupní parametr, který odkazuje na vyrovnávací paměť znaků a vyrovnávací paměť je ukončena hodnotou null.
#include <sal.h>
// Incorrect
void Func1(_Out_ PSTR pFileName, size_t n);
// Correct
void Func2(_Out_writes_(n) PSTR wszFileName, size_t n);
Anotace je _In_ PCSTR
běžná a užitečná. Odkazuje na vstupní řetězec, který má ukončení hodnoty null, protože předběžná podmínka _In_
umožňuje rozpoznat řetězec ukončený hodnotou null.
_In_ WCHAR* p
_In_ WCHAR* p
říká, že je vstupní ukazatel p
, který odkazuje na jeden znak. Ve většině případů to ale pravděpodobně není specifikace, která je určena. Místo toho je pravděpodobně určena specifikace pole s ukončenou hodnotou null; k tomu použijte _In_ PWSTR
.
#include <sal.h>
// Incorrect
void Func1(_In_ WCHAR* wszFileName);
// Correct
void Func2(_In_ PWSTR wszFileName);
Chybí správná specifikace ukončení hodnoty null. K nahrazení typu použijte odpovídající STR
verzi, jak je znázorněno v následujícím příkladu.
#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_
Pokud je parametr ukazatelem a chcete vyjádřit rozsah hodnoty prvku, na který odkazuje ukazatel, použijte _Deref_out_range_
místo _Out_range_
. V následujícím příkladu je rozsah *pcbFilled vyjádřen, nikoli 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)
není pro některé nástroje nezbytně nutné, protože ho lze odvodit z _Out_writes_to_(cbSize,*pcbFilled)
, ale je zde uvedena pro úplnost.
Nesprávný kontext v _When_
Další běžnou chybou je použít závěrečné vyhodnocení předpokladů. V následujícím příkladu _Requires_lock_held_
je předběžná podmínka.
#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);
Výraz return
odkazuje na hodnotu po stavu, která není k dispozici v představovém stavu.
TRUE
v _Success_
Pokud je funkce úspěšná, pokud je návratová hodnota nenulová, použijte return != 0
jako podmínku úspěchu místo return == TRUE
. Nenulová neznamená nutně ekvivalenci se skutečnou hodnotou, kterou kompilátor poskytuje TRUE
. Parametr, který _Success_
má být výrazem, a následující výrazy jsou vyhodnoceny jako ekvivalentní: return != 0
, return != false
, return != FALSE
a return
bez parametrů nebo 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á
U referenční proměnné použila předchozí verze SAL implicitní ukazatel jako cíl poznámky a vyžadovala přidání __deref
poznámek připojených k referenční proměnné. Tato verze používá samotný objekt a nevyžaduje _Deref_
.
#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
);
Poznámky k vrácených hodnotám
Následující příklad ukazuje běžný problém s návratovou hodnotou poznámek.
#include <sal.h>
// Incorrect
_Out_opt_ void *MightReturnNullPtr1();
// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();
V tomto příkladu se říká, _Out_opt_
že ukazatel může být NULL
součástí předběžné podmínky. Předběžné podmínky však nelze použít na vrácenou hodnotu. V tomto případě je _Ret_maybenull_
správná poznámka .
Viz také
Použití poznámek SAL ke snížení vad kódu C/C++
Porozumění SAL
Přidávání poznámek k parametrům funkce a vráceným hodnotám
Přidávání poznámek k chování funkce
Přidávání poznámek ke strukturám a třídám
Přidávání poznámek k chování uzamčení
Určení, kdy a kde se má anotace použít
Vnitřní funkce