Sdílet prostřednictvím


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_OUTnebo 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 != FALSEa 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