ベスト プラクティスと例 (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_
注釈を使用することをお勧めします。 この "defensive" 修飾子は特定の注釈を変更し、呼び出しの時点で、インターフェイスが厳密に検査されるはずであることを示しますが、実装本体では不適切なパラメーターが渡される可能性があることを想定する必要があります。 その場合、呼び出し元がNULL
を渡そうとすると呼び出し元がエラーを受け取るが、パラメーターがNULL
場合と同様に関数本体が分析され、ポインターを最初にチェックせずにポインターを逆参照しようとすると、NULL
フラグが設定されていることを示すために、_In_ _Pre_defensive_
が推奨されます。 信頼できるパーティが呼び出し元と想定され、信頼できないコードが呼び出される側のコードであるコールバックで使用する場合は、_Post_defensive_
注釈も使用できます。
_Out_writes_
次の例は、_Out_writes_
のよくある誤用を示しています。
#include <sal.h>
// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
DWORD size
);
注釈 _Out_writes_
は、バッファーがあることを表します。 cb
バイトが割り当て済みで、最初のバイトが終了時に初期化されています。 この注釈は厳密には間違いではなく、割り当てられたサイズを表すのに役立ちます。 ただし、関数が初期化する要素の数は示されません。
次の例は、バッファーの初期化された部分の正確なサイズを完全に指定する、3 種類の正しい方法を示しています。
#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
のような注釈が一般的で便利です。 この注釈は、_In_
の事前条件によって NULL で終わる文字列を認識できるので、NULL 終了の入力文字列を指します。
_In_ WCHAR* p
_In_ WCHAR* p
は、1 文字を指す入力ポインター 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_
の代わりに使用します。 次の例では、pcbFilled ではなく *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
);
_Out_writes_to_(cbSize,*pcbFilled)
から推察できるので _Deref_out_range_(0, cbSize)
は一部のツールでは厳密には必須ではありませんが、完全を期してここに示しています。
_When_
の間違ったコンテキスト
もう 1 つのよくある間違いは、事後状態の評価を事前条件に使用することです。 次の例で、_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
」
戻り値が 0 以外の際に関数が成功する場合は、return != 0
を return == TRUE
の代わりに成功条件として使用します。 0 以外の値は、コンパイラが 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 について
関数パラメーターおよび戻り値の注釈設定
関数の動作に注釈を付ける
構造体とクラスに注釈を付ける
ロック動作に注釈を付ける
注釈を適用するタイミングと場所の指定
組み込み関数