最佳作法和範例 (SAL)

以下是一些充分利用原始程式碼注釋語言 (SAL) 的方法,並避免一些常見問題。

_In_

如果函式應該寫入專案,請使用 _Inout_ 而非 _In_ 。 這與從舊版宏自動轉換成 SAL 的情況有關。 在 SAL 之前,許多程式設計人員會使用宏做為批註,這些名稱的名稱為 INOUTIN_OUT 或 變體的宏。 雖然我們建議您將這些宏轉換成 SAL,但是當您轉換這些宏時,我們也建議您小心,因為程式碼可能已經變更,因為原始原型已撰寫,而舊的宏可能不再反映程式碼的用途。 請特別小心 OPTIONAL 批註宏,因為它經常不正確地放置,例如,在逗號的錯誤端。

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

如果不允許呼叫端傳入 Null 指標,請使用 _In__Out_ ,而不是 _In_opt__Out_opt_ 。 這甚至適用于檢查其參數的函式,並在不應該發生時 NULL 傳回錯誤。 雖然函式會檢查其參數是否有非預期 NULL 且正常傳回是良好的防禦性編碼作法,但不表示參數注釋可以是選擇性類型 ( _*Xxx*_opt_ )。

#include <sal.h>

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

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

_Pre_defensive__Post_defensive_

如果函式出現在信任界限中,建議您使用 _Pre_defensive_ 註釋。 「防禦性」修飾詞會修改某些批註,指出在呼叫點,介面應該嚴格檢查,但在實作主體中,它應該假設可能會傳遞不正確的參數。 在此情況下, _In_ _Pre_defensive_ 最好在信任界限上指出,雖然呼叫端在嘗試傳遞 NULL 時收到錯誤,但會分析函式主體,就像是 參數 NULL 一樣,而且任何嘗試取值指標而不先檢查指標 NULL 時都會加上旗標。 _Post_defensive_ 註釋也可以用於回呼,其中信任的一方會假設為呼叫端,而不受信任的程式碼為被呼叫的程式碼。

_Out_writes_

下列範例示範 的常見誤用 _Out_writes_

#include <sal.h>

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

批註 _Out_writes_ 表示您有緩衝區。 其已 cb 配置位元組,並在結束時初始化第一個位元組。 此批註並非完全錯誤,而且表示已配置的大小會很有説明。 不過,它不會指出函式初始化的元素數目。

下一個範例示範三種正確方式,以完整指定已初始化部分的確切大小。

#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

的使用 _Out_ PSTR 幾乎總是錯誤的。 這個組合會解譯為具有指向字元緩衝區且緩衝區為 Null 終止的輸出參數。

#include <sal.h>

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

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

類似 的 _In_ PCSTR 批註很常見且實用。 它會指向具有 Null 終止的輸入字串,因為 前置 _In_ 條件允許辨識以 Null 終止的字串。

_In_ WCHAR* p

_In_ WCHAR* p 說有一個輸入指標 p 指向一個字元。 不過,在大部分情況下,這可能不是預期的規格。 相反地,可能的用途是指定以 Null 終止的陣列;若要這樣做,請使用 _In_ PWSTR

#include <sal.h>

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

// Correct
void Func2(_In_ PWSTR wszFileName);

遺漏 Null 終止的適當規格很常見。 使用適當的 STR 版本來取代類型,如下列範例所示。

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

如果參數是指標,而且您想要表示指標所指向之專案值的範圍,請使用 _Deref_out_range_ 而非 _Out_range_ 。 在下列範例中,表示 *bpcFilled 的範圍,而非pcpcfilled。

#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) 某些工具並非絕對必要,因為它可以從 推斷 _Out_writes_to_(cbSize,*pcbFilled) 出來,但這裡會顯示為完整性。

中的錯誤內容 _When_

另一個常見的錯誤是針對前置條件使用狀態後評估。 在下列範例中, _Requires_lock_held_ 是前置條件。

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

運算式 return 是指在預先狀態中無法使用的後置狀態值。

_Success_ 中的 TRUE

如果傳回值非零時函式成功,請使用 return != 0 作為成功條件,而不是 return == TRUE 。 非零不一定表示與編譯器提供 TRUE 的實際值相等。 _Success_ 的參數是運算式,而且下列運算式會評估為相等:return != 0return != falsereturn != FALSEreturn,且不含參數或比較。

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

參考變數

對於參考變數,舊版 SAL 使用隱含指標做為注釋目標,並且需要加入 __deref 附加至參考變數的批註。 此版本會使用 物件本身,而且不需要 _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
);

傳回值的注釋

下列範例顯示傳回值批註中常見的問題。

#include <sal.h>

// Incorrect
_Out_opt_ void *MightReturnNullPtr1();

// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();

在此範例中, _Out_opt_ 表示指標可能是 NULL 前置條件的一部分。 不過,前置條件無法套用至傳回值。 在此情況下,正確的注釋為 _Ret_maybenull_

另請參閱

使用 SAL 注釋來減少 C/C++ 程式碼缺失
了解 SAL
批註函式參數和傳回值
批註函式行為
批註結構與類別
批註鎖定行為
指定批註套用的時機和位置
內建函式