Procedure consigliate ed esempi (SAL)

Ecco alcuni modi per sfruttare al meglio il linguaggio di annotazione del codice sorgente (SAL) ed evitare alcuni problemi comuni.

_In_

Se la funzione deve scrivere nell'elemento , usare _Inout_ anziché _In_. Questo è rilevante nei casi di conversione automatica da macro meno recenti a SAL. Prima di SAL, molti programmatori usavano macro come commenti, ovvero macro denominate IN, OUT, IN_OUTo varianti di questi nomi. Sebbene sia consigliabile convertire queste macro in SAL, è consigliabile prestare attenzione anche quando si convertono le macro perché il codice potrebbe essere stato modificato dopo la scrittura del prototipo originale e la vecchia macro potrebbe non riflettere più le operazioni del codice. Prestare particolare attenzione alla OPTIONAL macro di commento perché viene inserita spesso in modo errato, ad esempio sul lato sbagliato di una virgola.

#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_

Se il chiamante non è autorizzato a passare un puntatore Null, usare _In_ o _Out_ anziché _In_opt_ o _Out_opt_. Questo vale anche per una funzione che controlla i parametri e restituisce un errore se è NULL quando non deve essere. Anche se la presenza di una funzione controlla se il parametro è imprevisto NULL e restituito normalmente è una buona procedura di codifica difensiva, non significa che l'annotazione del parametro può essere di un tipo facoltativo (_*Xxx*_opt_).

#include <sal.h>

// Incorrect
void Func1(_Out_opt_ int *p1)
{
    *p = 1;
}

// Correct
void Func2(_Out_ int *p1)
{
    *p = 1;
}

_Pre_defensive_ e _Post_defensive_

Se in una funzione è presente un limite di attendibilità, si consiglia di utilizzare l'annotazione _Pre_defensive_. Il modificatore "difensivo" modifica determinate annotazioni per indicare che, al momento della chiamata, l'interfaccia deve essere controllata rigorosamente, ma nel corpo dell'implementazione deve presupporre che sia possibile passare parametri non corretti. In tal caso, _In_ _Pre_defensive_ è preferibile in corrispondenza di un limite di attendibilità per indicare che anche se un chiamante riceve un errore se tenta di passare NULL, il corpo della funzione viene analizzato come se il parametro fosse NULLe qualsiasi tentativo di dereferenziare il puntatore senza prima verificarlo NULL viene contrassegnato. Un'annotazione _Post_defensive_ può inoltre essere utilizzata nei callback, dove si assume che la parte attendibile sia il chiamante e il codice non attendibile sia il codice chiamato.

_Out_writes_

Nell'esempio seguente viene illustrato un uso improprio comune di _Out_writes_.

#include <sal.h>

// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
    DWORD size
);

L'annotazione _Out_writes_ indica che si dispone di un buffer. Dispone cb di byte allocati, con il primo byte inizializzato all'uscita. Questa annotazione non è strettamente errata ed è utile esprimere le dimensioni allocate. Tuttavia, non indica quanti elementi inizializza la funzione.

Nell'esempio seguente vengono illustrati tre modi corretti per specificare completamente le dimensioni esatte della parte inizializzata del buffer.

#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

L'uso di _Out_ PSTR è quasi sempre sbagliato. Questa combinazione viene interpretata come avere un parametro di output che punta a un buffer di caratteri e che il buffer è con terminazione Null.

#include <sal.h>

// Incorrect
void Func1(_Out_ PSTR pFileName, size_t n);

// Correct
void Func2(_Out_writes_(n) PSTR wszFileName, size_t n);

Un'annotazione come _In_ PCSTR è comune e utile. Punta a una stringa di input con terminazione Null perché la precondizione di _In_ consente il riconoscimento di una stringa con terminazione Null.

_In_ WCHAR* p

_In_ WCHAR* p dice che c'è un puntatore p di input che punta a un carattere. Tuttavia, nella maggior parte dei casi, questo probabilmente non è la specifica prevista. Invece, ciò che è probabilmente previsto è la specifica di una matrice con terminazione Null; a tale scopo, usare _In_ PWSTR.

#include <sal.h>

// Incorrect
void Func1(_In_ WCHAR* wszFileName);

// Correct
void Func2(_In_ PWSTR wszFileName);

La specifica corretta della terminazione Null è comune. Usare la versione appropriata STR per sostituire il tipo, come illustrato nell'esempio seguente.

#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_

Se il parametro è un puntatore e si desidera esprimere l'intervallo del valore dell'elemento a cui punta il puntatore, usare _Deref_out_range_ anziché _Out_range_. Nell'esempio seguente, l'intervallo di *pcbFilled è espresso, non 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) non è strettamente necessario per alcuni strumenti perché può essere dedotto da _Out_writes_to_(cbSize,*pcbFilled), ma è illustrato qui per completezza.

Contesto errato in _When_

Un altro errore comune consiste nell'usare la valutazione post-stato per le precondizioni. Nell'esempio seguente è _Requires_lock_held_ una precondizione.

#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);

L'espressione return fa riferimento a un valore post-stato che non è disponibile nello stato precedente.

TRUE in _Success_

Se la funzione ha esito positivo quando il valore restituito è diverso da zero, usare return != 0 come condizione di esito positivo anziché return == TRUE. Diverso da zero non significa necessariamente equivalenza al valore effettivo fornito dal compilatore per TRUE. Il parametro _Success_ è un'espressione e le seguenti espressioni vengono valutate come equivalenti: return != 0, return != false, return != FALSE e return senza parametri o confronti.

// 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
);

Variabile di riferimento

Per una variabile di riferimento, la versione precedente di SAL usava il puntatore implicito come destinazione dell'annotazione e richiedeva l'aggiunta di un __deref oggetto alle annotazioni associate a una variabile di riferimento. Questa versione usa l'oggetto stesso e non richiede _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
);

Annotazioni sui valori restituiti

Nell'esempio seguente viene illustrato un problema comune nelle annotazioni dei valori restituiti.

#include <sal.h>

// Incorrect
_Out_opt_ void *MightReturnNullPtr1();

// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();

In questo esempio il _Out_opt_ puntatore potrebbe essere NULL parte della precondizione. Tuttavia, le precondizioni non possono essere applicate al valore restituito. In questo caso, l'annotazione corretta è _Ret_maybenull_.

Vedi anche

Uso delle annotazioni SAL per ridurre i difetti del codice C/C++
Informazioni su SAL
Annotazione dei parametri della funzione e valori restituiti
Annotazione del comportamento della funzione
Annotazione di struct e classi
Annotazione del comportamento di blocco
Specifica di quando e dove si applica un'annotazione
Funzioni intrinseche