下面是一些用于捕获大多数在源代码注释语言 (SAL) 在 + 外避免一些常见问题。
_In_
如果该函数应向该组件编写,请使用 _Inout_ 而不是 _In_。这是密切相关的在以下情况下从旧宏的自动转换为 SAL。在 SAL 之前,许多程序员使用了宏作为名为 IN、OUT、这些名称 IN_OUT或变量的注释宏。虽然建议您将这些宏为 SAL,我们还敦促要小心,在转换时,因为代码可能已经更改了,因为原原型编写的,并且旧宏可能不再反映什么代码。时应特别小心 OPTIONAL 注释宏,因为它不正确 (例如经常放置,反面逗号。
// Incorrect
void Func1(_In_ int *p1)
{
if (p1 == NULL)
return;
*p1 = 1;
}
// Correct
void Func2(_Inout_ PCHAR p1)
{
if (p1 == NULL)
return;
*p1 = 1;
}
_opt_
如果调用方在 null 指针、使用 _In_ 或 _Out_ 不允许而不是 _In_opt_ 或 _Out_opt_。这适用于甚至检查其参数并返回错误的功能,则为空,则不应为时。虽然具有功能测试其意外空参数并返回正常好防御性代码的做法,它并不意味着批注参数可以是一个选项类型 (_Xxx_opt_)。
// 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,将分析函数体,则该参数可能空,因此,所有尝试取消引用指针,而无需先检查该空将标记。_Post_defensive_ 批注也可用,用于在该信任的一方假定是在调用方的回调,并且不受信任的代码是调用代码。
_Out_writes_
下面的示例演示 _Out_writes_常见滥用。
// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
DWORD size
);
批注将 _Out_writes_ 表示有缓冲区。它有 cb 字节分配,当第一个字节初始化退出。此批注不是严格 false,并且表示赋值的大小很有用。但是,它不调用多少个元素由函数初始化。
下一个示例演示三个正确方法完全指定缓冲区的初始化的确切大小。
// 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 结尾。
// 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。
// Incorrect
void Func1(_In_ WCHAR* wszFileName);
// Correct
void Func2(_In_ PWSTR wszFileName);
缺少 NULL 终止的相应规范是通用的。如下面的示例所示,使用适当的 STR 版本替换该类型。
// 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。
// 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_ 是前置条件。
// 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);
该表达式 result 引用不在之前状态的一个后期状态值。
为 true。_Success_
如果函数成功,则返回值是非零,请使用 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_。
// 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
);
批注返回值
下面的示例演示一个常见的问题"值批注。
// Incorrect
_Out_opt_ void *MightReturnNullPtr1();
// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();
在此示例中,_Out_opt_ 添加指针可能为 null 为前置条件的一部分。但是,前置条件不能应用于返回值。在这种情况下,正确的批注是 _Ret_maybenull_。