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