最佳作法和範例 (SAL)
以下是一些充分利用原始程式碼註釋語言 (SAL) 的方法,並避免一些常見問題。
_In_
如果函式應該寫入專案,請使用 _Inout_
而非 _In_
。 這與從舊版巨集自動轉換成 SAL 的情況有關。 在 SAL 之前,許多程式設計人員會使用巨集做為批注,這些名稱的名稱為 IN
、 OUT
、 IN_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 != 0
、return != false
、return != FALSE
和 return
,且不含參數或比較。
// 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
批注函式參數和傳回值
批註函式行為
批注結構與類別
批註鎖定行為
指定註套用的時機和位置
內建函式