最佳做法和示例 (SAL)

下面是一些用于捕获大多数在源代码注释语言 (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_。

请参见

参考

对函数参数和返回值进行批注

对函数行为进行批注

批注结构和类

对锁定行为进行批注

指定何时以及在何处应用批注

内部函数

概念

了解 SAL

其他资源

使用 SAL 批注以减少 C/C++ 代码缺陷