__attribute__ 是 GNU C (GCC) 和 Clang 编译器提供的一个强大的扩展功能,它允许开发者向编译器提供更多的信息,从而进行更严格的代码检查、优化代码生成,或者改变函数/变量的某些行为。

__attribute__ 的语法格式通常如下:
__attribute__((attribute-list))
这个属性列表可以包含一个或多个属性,用逗号分隔。
__attribute__ 可以用于多种场景,主要包括:
- 函数
- 变量
- 类型
下面我们分类介绍一些最常用和最重要的 __attribute__。

用于函数的 __attribute__
这是 __attribute__ 最常见的用法之一,可以用来改变函数的调用约定、行为和检查方式。
a. always_inline
作用: 强制编译器将函数内联展开,而不是生成函数调用的代码,即使没有使用 -O (优化) 选项,编译器也必须遵循这个指令。
场景: 对于非常小且频繁调用的函数(如获取/设置器、数学库中的简单函数),内联可以消除函数调用的开销,提升性能。
示例:

// 强制内联
static inline int square(int x) __attribute__((always_inline));
static inline int square(int x) {
return x * x;
}
int main() {
int result = square(5); // 这里不会调用 square 函数,而是直接替换成 5*5
return 0;
}
b. noinline
作用: 与 always_inline 相反,禁止编译器内联函数,即使开启了优化,编译器也不会内联这个函数。
场景: 调试时,有时需要确保函数调用栈完整,不希望函数被内联,或者,对于某些复杂的函数,强制内联可能导致代码体积急剧增大,此时应避免内联。
示例:
// 禁止内联
void log_message(const char* msg) __attribute__((noinline));
void log_message(const char* msg) {
// ... 实际的日志记录代码 ...
}
int main() {
log_message("Hello"); // 这里一定会生成一个函数调用
return 0;
}
c. deprecated
作用: 标记一个函数或变量为“已弃用”,当其他代码使用它时,编译器会给出警告。
场景: 当某个 API (函数或变量) 即将被移除或替换时,使用 deprecated 可以通知开发者停止使用它,并迁移到新的 API。
示例:
// 标记函数为已弃用
void old_function() __attribute__((deprecated));
void old_function() {
printf("This is the old function.\n");
}
// 标记变量为已弃用
int legacy_var __attribute__((deprecated)) = 100;
int main() {
old_function(); // 编译时会产生警告: warning: 'old_function' is deprecated
printf("%d\n", legacy_var); // 编译时会产生警告: warning: 'legacy_var' is deprecated
return 0;
}
d. error 和 warning
作用: 当调用被标记的函数时,error 会直接将调用视为编译错误并停止编译;warning 则会产生一个编译警告。
场景: error 用于彻底移除某个 API,任何尝试使用它的代码都无法通过编译。warning 用于发出强烈的警告,但允许编译继续。
示例:
// 调用此函数将导致编译失败
void forbidden_function() __attribute__((error("This function is not allowed to be called")));
// 调用此函数将产生警告
void use_with_caution() __attribute__((warning("This function may have side effects")));
int main() {
// forbidden_function(); // 取消注释此行,编译器会报错并停止
use_with_caution(); // 编译器会发出警告
return 0;
}
e. format
作用: 帮助编译器检查格式化字符串(如 printf, scanf 系列)的参数是否匹配,这是防止因格式化字符串错误导致程序崩溃或安全漏洞的利器。
它需要两个参数:
printf或scanf等,表示格式化函数的类型。- 格式化字符串参数的位置(从 1 开始计数)。
- (可选) 第一个可变参数的位置。
场景: 任何自己封装的、与 printf/scanf 行为相似的函数。
示例:
// 定义一个自己的日志函数,行为类似 printf
void my_log(const char* format, ...) __attribute__((format(printf, 1, 2)));
void my_log(const char* format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args); // 使用标准库的 vprintf
va_end(args);
}
int main() {
my_log("This is an int: %d\n", 42); // 正确,编译器不警告
my_log("This is a string: %s\n", "hello"); // 正确
// my_log("This is a string: %d\n", "hello"); // 错误,编译器会警告: format '%d' expects argument of type 'int', but argument 2 has type 'char *'
return 0;
}
用于变量的 __attribute__
a. used
作用: 告诉编译器,即使这个变量看起来没有被使用(比如没有在任何地方被引用),也不要将其优化掉(不放入 .bss 段或 .data 段)。
场景: 主要用于与汇编代码交互,你可能定义了一个变量,但只在汇编代码中通过其地址访问它,C 代码层面没有任何引用,没有 used,编译器可能会“聪明地”将其删除。
示例:
// 这个变量在 C 代码中不会被使用,但汇编代码可能会用到它
int some_var __attribute__((used)) = 123;
int main() {
// some_var 在这里没有被使用
return 0;
}
b. unused
作用: 与 used 相反,告诉编译器这个变量可能不会被使用,即使它未被引用,也不要产生警告。
场景: 用于条件编译,一个变量只在某个宏定义时才会被使用,如果没定义,编译器可能会警告这个变量是“未使用的”,加上 unused 可以消除这个警告。
示例:
int config_var __attribute__((unused)) = 0;
int main() {
// 假设下面的代码被注释掉了,config_var 变成了未使用变量
// printf("%d\n", config_var);
return 0;
}
用于类型的 __attribute__
a. packed
作用: 告诉编译器,不要在结构体或联合体的成员之间插入任何填充字节(padding),以使其紧凑地排列在内存中。
场景:
- 硬件寄存器映射: 当一个 C 结构体需要精确地对应到一个硬件寄存器布局时,不能有任何填充。
- 网络协议/文件格式: 当需要解析二进制数据流(如网络包、文件头)时,必须确保内存布局与二进制格式完全一致。
示例:
// 没有使用 packed
struct normal_struct {
char a;
int b;
};
// 在大多数 32/64 位系统上,sizeof(struct normal_struct) 是 8,因为 char 后面会填充 3 个字节。
// 使用 packed
struct packed_struct {
char a;
int b;
} __attribute__((packed));
// sizeof(struct packed_struct) 是 5,成员之间没有填充。
int main() {
printf("Size of normal_struct: %zu\n", sizeof(struct normal_struct)); // 输出 8
printf("Size of packed_struct: %zu\n", sizeof(struct packed_struct)); // 输出 5
return 0;
}
其他重要属性
a. constructor 和 destructor
作用:
constructor: 标记的函数会在main()函数执行之前自动被调用。destructor: 标记的函数会在main()函数执行完毕(或exit()被调用)之后自动被调用。
场景: 用于执行初始化和清理任务,如单例模式的初始化、资源注册、全局状态设置等。
示例:
#include <stdio.h>
// 优先级数字越小,执行优先级越高
void init_a() __attribute__((constructor(101)));
void init_b() __attribute__((constructor(102)));
void init_a() {
printf("Constructor A (priority 101) called.\n");
}
void init_b() {
printf("Constructor B (priority 102) called.\n");
}
void cleanup() __attribute__((destructor));
void cleanup() {
printf("Destructor called.\n");
}
int main() {
printf("Inside main().\n");
return 0;
}
执行顺序:
Constructor A (priority 101) called.
Constructor B (priority 102) called.
Inside main().
Destructor called.
如何检查兼容性?
__attribute__ 是 GNU 的扩展,并非标准 C 的一部分,如果你需要编写可移植的代码,或者想知道某个属性是否被支持,可以使用宏进行判断。
最常用的宏是 __GNUC__,它在 GCC 和 Clang 编译器中都会被定义。
示例:
#if defined(__GNUC__)
// 这是一个 GCC 或 Clang 编译器,可以使用 __attribute__
void my_function() __attribute__((deprecated));
#else
// 其他编译器(如 MSVC),使用它自己的方式标记为弃用
#pragma message("my_function is deprecated")
void my_function() {}
#endif
__attribute__ 是 C 语言(特别是 GCC/Clang 生态)中一个非常强大的工具,它虽然不属于 C 标准核心,但在系统编程、嵌入式开发和高性能应用中极为常见。
| 属性 | 作用 | 主要场景 |
|---|---|---|
always_inline |
强制内联函数 | 性能优化,小函数 |
noinline |
禁止内联函数 | 调试,控制代码大小 |
deprecated |
标记为已弃用 | API 版本管理,平滑过渡 |
error/warning |
产生编译错误/警告 | 彻底移除 API 或发出强烈警告 |
format |
检查格式化字符串参数 | 封装 printf/scanf 类函数 |
used |
防止变量被优化掉 | 与汇编交互,保留未使用变量 |
unused |
避免未使用变量警告 | 条件编译,避免误报 |
packed |
结构体紧凑排列,无填充 | 硬件寄存器,二进制协议解析 |
constructor/destructor |
main 前后自动执行 |
全局初始化,资源注册 |
掌握 __attribute__ 可以让你的 C 代码更健壮、更高效,并且能更好地利用编译器的强大功能。
