__in 并不是 C 语言标准库的一部分,也不是 C 语言本身的关键字,它是一个由 Microsoft 在其 Windows SDK (Software Development Kit) 中定义的、用于增强代码可读性和进行静态分析的宏。

__in 是一个文档注释宏,它告诉代码阅读者(以及像 Visual Studio 的静态分析工具 PVS-Studio 或 Prefast)这个参数是一个输入参数。
__in 的作用和目的
想象一下一个函数,它接收多个指针参数,这些指针可能指向要读取的数据、要写入的数据,或者两者都有,如果不加任何说明,阅读代码的人需要去查看函数内部实现才能确定每个指针的用途。
__in、__out、__inout 等宏就是为了解决这个问题而引入的。
__in 的核心作用:

- 文档化:清晰地表明该参数是一个输入参数,调用者负责确保在调用函数前,该参数指向的数据是有效且准备好的,函数内部只会读取这个指针指向的数据,而不会修改它。
- 静态分析:允许编译器或静态分析工具检查代码的正确性,工具可以警告你:
- 如果一个标记为
__in的指针参数是NULL,而函数需要解引用它。 - 如果调用者试图修改一个标记为
__in的参数所指向的数据(虽然这本身不会阻止编译,但分析工具会发出警告,因为它违反了__in的契约)。
- 如果一个标记为
__in 的实际定义
在 Windows SDK 的头文件(如 sal.h 或 sal2.h)中,__in 通常被定义为一个空宏,或者一个带有特定属性(用于 GCC/Clang)的宏。
对于 Visual Studio 的 MSVC 编译器,它的定义通常是这样的:
// 在 sal.h 或类似文件中 #define __in
看到这里你可能会惊讶,它竟然是空的!是的,对于 MSVC __in 主要是一个“标签”或“标记”,编译器在编译时并不会因为这个宏的存在而生成额外的代码,它的价值在于:
- 代码可读性:程序员能一眼看懂。
- 静态分析:专门的工具(如 PVS-Studio)会识别这些宏并进行检查。
对于 GCC 和 Clang,它可能被定义为一个编译器属性,

// 在 sal.h 或类似文件中 #define __in __attribute__((access(read_only, 1)))
这里的 __attribute__((access(read_only, 1))) 告诉编译器,函数的第一个参数 (1) 是只读的,编译器会据此进行更严格的检查。
使用示例
让我们通过一个例子来理解 __in、__out 和它们如何一起工作。
假设我们要编写一个函数,该函数接收一个源字符串和一个目标缓冲区,将源字符串复制到目标缓冲区中。
没有 __in/__out 的版本:
#include <stdio.h>
#include <string.h>
// 不带 SAL 注释的函数
void copy_string(const char* src, char* dest, int dest_size) {
// 阅读者必须看函数体才能明白 src 是输入,dest 是输出
if (strlen(src) + 1 > dest_size) {
printf("Destination buffer is too small!\n");
return;
}
strcpy(dest, src);
}
int main() {
const char* message = "Hello, World!";
char buffer[50];
copy_string(message, buffer, sizeof(buffer));
printf("Copied string: %s\n", buffer);
return 0;
}
在这个版本里,你需要读 strcpy(dest, src) 这一行才能明白 src 是输入,dest 是输出。
使用 __in/__out 的版本:
#include <stdio.h>
#include <string.h>
// 在实际项目中,你需要包含相应的头文件,#include <sal.h>
// 为了演示,我们在这里手动定义它们
#ifndef __in
#define __in
#endif
#ifndef __out
#define __out
#endif
#ifndef __inout
#define __inout
#endif
#ifndef __out_ecount
#define __out_ecount(x)
#endif
// 带有 SAL 注释的函数
void copy_string_ex(__in const char* src, __out_ecount(dest_size) char* dest, int dest_size) {
// 函数签名本身就清晰地说明了参数的用途
if (strlen(src) + 1 > dest_size) {
printf("Destination buffer is too small!\n");
return;
}
strcpy(dest, src);
}
int main() {
const char* message = "Hello, World!";
char buffer[50];
copy_string_ex(message, buffer, sizeof(buffer));
printf("Copied string: %s\n", buffer);
return 0;
}
代码解读:
__in const char* src:__in: 告诉调用者和分析工具,src是一个输入参数。const: C 语言标准关键字,进一步从语言层面保证了src指向的内容不会被修改。__in和const在这里起到了双重保障的作用。
__out_ecount(dest_size) char* dest:__out: 告诉调用者和分析工具,dest是一个输出参数,调用者不应依赖调用前dest中的值,函数将负责写入数据。_ecount(dest_size): 这是一个更具体的__out扩展,表示写入到dest的数据最多为dest_size字节,静态分析工具可以用它来检查缓冲区溢出。
int dest_size: 这是一个普通的值参数,用于传递缓冲区大小。
常见的 SAL 宏
除了 __in,Windows SDK 还定义了一系列相关的宏,它们共同构成了 SAL (Source Annotation Language):
| 宏 | 全称 | 描述 |
|---|---|---|
__in |
Input | 参数是输入,函数只读取该指针指向的数据。 |
__out |
Output | 参数是输出,函数会写入该指针指向的数据,调用者不应假设其初始值。 |
__inout |
Input/Output | 参数既是输入也是输出,函数会先读取,再写入。 |
__in_z |
Input, Null-terminated | 输入参数,指向以空字符结尾的字符串。 |
__out_ecount(size) |
Output, Element Count | 输出参数,指向一个缓冲区,函数会写入最多 size 个字节的数据。 |
__inout_bcount(size) |
Input/Output, Byte Count | 输入/输出参数,指向一个缓冲区,函数会读取和写入最多 size 个字节。 |
__deref |
Dereference | 用于指针的指针,表示注释应用于解引用后的指针。__in __deref const char* 表示指向一个 const char 的指针,而这个 const char 是输入。 |
__in是什么? 它是 Microsoft Windows SDK 中的一个宏,用于源代码注释。- 它的作用是什么?
- 提高可读性:清晰地标明函数参数是输入、输出还是双向的。
- 辅助静态分析:帮助代码分析工具(如 PVS-Studio)发现潜在的 bug,如使用 NULL 指针、缓冲区溢出等。
- 它会影响生成的代码吗? 在 MSVC 中,它本身不生成任何代码,它是一个“元数据”或“标签”。
- 我应该使用它吗?
- 如果你正在为 Windows 平台开发,特别是使用 Visual Studio,强烈推荐使用,它能极大地提升代码质量和可维护性。
- 如果你正在开发跨平台代码(例如在 Linux 上使用 GCC),并且你的编译器支持类似的属性(如
__attribute__),那么使用类似的注释也是一个好习惯。 - 如果你只是写一些简单的、一次性的脚本,那么不使用它也完全可以。
__in 是一种现代 C/C++ 开发中非常有用的文档和防御性编程工具,它让代码意图更加明确,并能帮助工具自动发现错误。
