缓冲区标识符?什么东西。好像从来没有见过,貌似也没听过啊。是不是又出自某个隐秘山谷里面的深谷幽兰。答案是否定的。缓冲区标识符更像是一种代码的锦上添花,完全不用了解,你依旧可以快乐的编写代码,但是这种快乐更像是大碗喝酒、大块吃肉的狂野;了解之后,编写代码会有种刺绣的感觉,是艺术的雕琢和精益求精,甚至而有点古典美出来,给人一见如故,再见沉迷之意。
啊!!什么玩意。真的这么玄乎。引入官方的解释:
SAL 是 Microsoft 源代码注释语言。 通过使用源代码注释,可以在代码中明确表明你的意图。 这些注释还能使自动化静态分析工具更准确地分析代码,明显减少假正和假负情况。
Microsoft 源代码注释语言 (SAL) 提供一组描述函数如何使用其参数的注释,有关注释的假设以及完成注释时的保证。 注释是在头文件中定义的。 适用于 C++ 的 Visual Studio 代码分析使用 SAL 注释来修改其函数分析。
SAL 定义了四种基本类型的参数,这些参数按使用模式进行分类。
可以通过各种方式使这四个基本注释更加明确。 默认情况下,带注释的指针参数假定为必需参数,它们必须为非 NULL,函数才能成功。 最常用的基本注释变体指示指针参数是可选参数,如果它为 NULL,函数仍可成功执行其工作。
_In_
注释_In_
注释表明:
1. 参数必须有效,并且不会修改。
2. 函数仅会从单元素缓冲区读取。
3. 调用方必须提供缓冲区并对其进行初始化。
4. `_In_ `指定“只读”。 常见的错误是将 _In_ 应用于应具有 _Inout_ 注释的参数。
5. 允许使用 `_In_`,但对于非指针标量,分析器将其忽略
void InCallee(_In_ int *pInt)
{
int i = *pInt;
}
void GoodInCaller()
{
int *pInt = new int;
*pInt = 5;
InCallee(pInt);
delete pInt;
}
void BadInCaller()
{
int *pInt = NULL;
InCallee(pInt); // pInt should not be NULL
}
如果对此示例使用 Visual Studio Code Analysis,它将验证调用方是否将非空指针传递给 pInt 初始化的缓冲区。 在这种情况下,pInt 指针不能为 NULL。
_In_opt_
注释_In_opt_
与 _In_
相同,只不过允许输入参数为 NULL,因此,函数应检查该参数。
void GoodInOptCallee(_In_opt_ int *pInt)
{
if(pInt != NULL) {
int i = *pInt;
}
}
void BadInOptCallee(_In_opt_ int *pInt)
{
int i = *pInt; // Dereferencing NULL pointer 'pInt'
}
void InOptCaller()
{
int *pInt = NULL;
GoodInOptCallee(pInt);
BadInOptCallee(pInt);
}
Visual Studio Code Analysis 会验证函数在访问缓冲区之前是否检查 NULL。
_Out_
注释_Out_
支持一种常见方案,即传入一个指向元素缓冲区的非空指针,并由函数初始化该元素。 调用方不必在调用之前初始化缓冲区;调用的函数承诺在返回结果之前对缓冲区进行初始化。
void GoodOutCallee(_Out_ int *pInt)
{
*pInt = 5;
}
void BadOutCallee(_Out_ int *pInt)
{
// Did not initialize pInt buffer before returning!
}
void OutCaller()
{
int *pInt = new int;
GoodOutCallee(pInt);
BadOutCallee(pInt);
delete pInt;
}
Visual Studio Code Analysis 验证此函数是否在 pInt 取消引用之前检查 NULL,如果 pInt 不是 NULL,此函数是否在返回结果之前初始化缓冲区。
_Inout_
注释_Inout_
用于注释函数可能更改的指针参数。 在调用之前指针必须指向有效的初始化数据,即使发生更改,也必须在返回时具有有效的值。 注释指定函数可以对单元素缓冲区进行自由地读取和写入。 调用方必须提供缓冲区并对其进行初始化。
备注:与 _Out_
一样, _Inout_
必须应用于可修改的值。
void InOutCallee(_Inout_ int *pInt)
{
int i = *pInt;
*pInt = 6;
}
void InOutCaller()
{
int *pInt = new int;
*pInt = 5;
InOutCallee(pInt);
delete pInt;
}
void BadInOutCaller()
{
int *pInt = NULL;
InOutCallee(pInt); // 'pInt' should not be NULL
}
Visual Studio Code Analysis 验证调用方是否将非 NULL 指针传递给 pInt 初始化的缓冲区,并且在返回前,pInt 是否仍为非 NULL 且缓冲区已初始化。
_Inout_opt_
注释_Inout_opt_
与 _Inout_
相同,只不过允许输入参数为 NULL,因此,函数应检查该参数。
void GoodInOutOptCallee(_Inout_opt_ int *pInt)
{
if(pInt != NULL) {
int i = *pInt;
*pInt = 6;
}
}
void BadInOutOptCallee(_Inout_opt_ int *pInt)
{
int i = *pInt; // Dereferencing NULL pointer 'pInt'
*pInt = 6;
}
void InOutOptCaller()
{
int *pInt = NULL;
GoodInOutOptCallee(pInt);
BadInOutOptCallee(pInt);
}
Visual Studio Code Analysis 验证此函数是否在访问缓冲区之前检查 NULL,如果 pInt 不是 NULL,此函数是否在返回结果之前初始化缓冲区。
_Outptr_
注释_Outptr_
用于为预期返回指针的参数进行注释。 参数本身不应为 NULL,调用的函数将返回非空指针,且该指针指向已初始化的数据。
void GoodOutPtrCallee(_Outptr_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 5;
*pInt = pInt2;
}
void BadOutPtrCallee(_Outptr_ int **pInt)
{
int *pInt2 = new int;
// Did not initialize pInt buffer before returning!
*pInt = pInt2;
}
void OutPtrCaller()
{
int *pInt = NULL;
GoodOutPtrCallee(&pInt);
BadOutPtrCallee(&pInt);
}
Visual Studio Code Analysis 验证调用方是否为 *pInt 传递非空指针,以及函数是否在返回结果之前初始化缓冲区。
_Outptr_opt_
注释_Outptr_opt_
与 _Outptr_
相同,只不过该参数为可选参数,调用方可以为该参数传入空指针。
void GoodOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 6;
if(pInt != NULL) {
*pInt = pInt2;
}
}
void BadOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 6;
*pInt = pInt2; // Dereferencing NULL pointer 'pInt'
}
void OutPtrOptCaller()
{
int **ppInt = NULL;
GoodOutPtrOptCallee(ppInt);
BadOutPtrOptCallee(ppInt);
}
Visual Studio Code Analysis 验证此函数是否在 *pInt 取消引用之前检查 NULL,以及此函数是否在返回结果之前初始化缓冲区。
_Success_
注释与 _Out_
组合大多数对象都可应用注释。 尤其可以对整个函数进行注释。 函数最明显的特征之一是它可以成功或失败。 但如缓冲区与其大小之间的关联一样,C/C++ 无法表示函数的成功或失败。 通过使用 _Success_
注释,可以了解函数成功的情况。 _Success_
注释参数只是一个表达式,当它为 true 时,指示函数已成功。 表达式可以是注释分析器能够处理的任何内容。 函数返回后的注释效果仅适用于函数成功时。 此示例演示 _Success_
如何与 _Out_
交互执行正确的操作。 可以使用关键字 return 来表示返回值。
_Success_(return != false) // Can also be stated as _Success_(return)
bool GetValue(_Out_ int *pInt, bool flag)
{
if(flag) {
*pInt = 5;
return true;
} else {
return false;
}
}
_Out_
注释使 Visual Studio Code Analysis 验证调用方是否将非空指针传递给 pInt 缓冲区,以及函数是否在返回结果之前初始化该缓冲区。
下面是一些准则:
注释所有指针参数。
提供值范围注释,以便 Code Analysis 可以确保缓冲区和指针的安全。
注释锁定规则和锁定副作用。 有关详细信息,请参阅注释锁定行为。
注释驱动程序属性和其他特定于域的属性。
more详细资料和高级用法,请移步官方网站 https://learn.microsoft.com/zh-cn/cpp/code-quality/using-sal-annotations-to-reduce-c-cpp-code-defects?view=msvc-170