什么是 __attribute__?
__attribute__ 是 GCC(GNU Compiler Collection)和 Clang 编译器提供的一个强大的扩展功能,它允许开发者向编译器提供额外的信息,从而影响代码的编译过程、优化方式或运行时行为。
它是一个“编译器提示”或“编译器指令”,让你能够告诉编译器一些标准 C 语言语法无法表达的事情。
基本语法
__attribute__ 的基本语法如下:
__attribute__((attribute-list))
它可以放在以下位置:
- 函数声明/定义后:用于修饰函数。
- 变量声明/定义后:用于修饰变量。
- 结构体/联合体成员后:用于修饰成员。
- 类型定义后:用于修饰
typedef。
attribute-list 是一个由逗号分隔的属性列表,((noreturn, warn_unused_result))。
注意:
__attribute__是两个下划线开头和结尾,这是 GCC 的约定。- 由于它是编译器扩展,不是标准 C 的一部分,使用它可能会降低代码的可移植性,但它在 Linux 内核、嵌入式系统等高性能领域被广泛使用。
常用 __attribute__ 属性详解
下面我们介绍一些最常用和最重要的 __attribute__ 属性,并附上示例。
noreturn
作用:告诉编译器,这个函数永远不会返回,这意味着函数内部要么会无限循环,要么会通过 exit(), abort() 等方式终止程序,执行流程不会“返回”到调用点。
优点:
- 优化:编译器可以省去为函数返回创建栈帧等操作,进行优化。
- 消除警告:可以消除
warning: 'noreturn' function does return这样的警告。 - 静态分析:帮助静态分析工具(如
clang-tidy)理解代码流,在noreturn函数调用之后的代码,会被判定为“不可达代码”,从而可能产生新的警告。
示例:
#include <stdlib.h>
// 告诉编译器,这个函数永远不会返回
void my_exit(void) __attribute__((noreturn));
void my_exit(void) {
printf("Exiting program...\n");
exit(1); // 调用 exit,确实不会返回
}
int main(void) {
int a = 10;
if (a > 0) {
my_exit(); // 调用 my_exit
}
// 因为 my_exit 是 noreturn,编译器知道这行代码永远不会执行
printf("This line will never be printed.\n");
return 0;
}
aligned(n)
作用:指定变量或结构体的对齐方式为 n 字节,对齐可以提高内存访问效率,避免性能下降,甚至在某些架构上是必须的。
示例:
// 强制变量 v 的内存地址必须是 16 的倍数
int var __attribute__((aligned(16)));
// 强制结构体 S 的内存对齐为 32 字节
struct S {
char c;
int i;
} __attribute__((aligned(32)));
int main(void) {
printf("Address of var: %p\n", (void*)&var);
// 输出地址的最低 4 位应该是 0,表示 16 字节对齐
struct S s;
printf("Address of s: %p\n", (void*)&s);
return 0;
}
packed
作用:告诉编译器不要在结构体成员之间或结构体末尾插入任何填充字节(padding),这可以最小化结构体的大小,但可能会以牺牲内存访问效率为代价。
用途:
- 与硬件寄存器交互,硬件要求特定的数据布局。
- 网络协议解析,数据包需要精确的字节对齐。
示例:
// 没有使用 packed,编译器可能会在 char 和 int 之间填充 3 个字节
struct normal_struct {
char c;
int i;
};
// sizeof(struct normal_struct) 通常是 8 (1 + 3 padding + 4)
// 使用 packed,编译器不会插入任何填充
struct packed_struct {
char c;
int i;
} __attribute__((packed));
// sizeof(struct packed_struct) 是 5 (1 + 4)
deprecated 和 unavailable
作用:标记一个函数或变量为“已弃用”或“不可用”,当开发者尝试使用它们时,编译器会产生警告或错误。
deprecated:产生警告,允许代码继续编译,但强烈建议不要使用。
unavailable:产生错误,阻止代码编译。
示例:
// 产生警告
void old_function(void) __attribute__((deprecated));
// 产生编译错误
void future_removed_function(void) __attribute__((unavailable));
void old_function(void) {
printf("This is an old function.\n");
}
int main(void) {
old_function(); // 编译时会产生警告
// future_removed_function(); // 这行会导致编译错误
return 0;
}
format (arch, arg_index)
作用:用于检查格式化字符串函数(如 printf, scanf)的参数类型是否正确,这是防止格式化字符串漏洞(Format String Vulnerability)的利器。
arch:printf或scanf。arg_index: 格式化字符串参数的位置(从 1 开始)。
示例:
#include <stdio.h>
// 告诉编译器,检查第一个参数(格式化字符串)和第三个参数(...中的第一个)
void my_printf(const char *fmt, ...) __attribute__((format(printf, 1, 3)));
void my_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
int main(void) {
int num = 10;
// 正确用法,编译正常
my_printf("The number is %d\n", num);
// 错误用法,传错了类型,编译器会警告
my_printf("The number is %s\n", num);
// 警告信息类似:format '%s' expects argument of type 'char *', but argument 3 has type 'int'
return 0;
}
const
作用:告诉编译器,这个函数除了返回值外,不会修改任何全局或静态变量,也不会有任何副作用(如 I/O 操作)。
优点:
- 强大的优化:编译器可以安全地缓存函数的返回结果,如果函数被多次调用,且参数相同,编译器可能会只计算一次并重用结果,甚至可能完全内联该函数。
示例:
// 告诉编译器,这个函数是纯函数,没有副作用
int square(int x) __attribute__((const));
int square(int x) {
return x * x;
}
int main(void) {
int a = 5;
// 编译器可能会优化成 a = 25;
a = square(5);
// 在一个循环中,这种优化效果更明显
long sum = 0;
for (int i = 0; i < 100; i++) {
sum += square(i); // 编译器可能会将 square(i) 的结果缓存
}
return 0;
}
section("section_name")
作用:将变量或函数放置在指定的 ELF 文件段中,这对于嵌入式开发至关重要,将初始化数据放在 .init_array 段,或将特定数据放在特定的内存区域。
示例:
// 将变量 my_data 放在名为 "my_data_section" 的自定义段中
int my_data __attribute__((section("my_data_section"))) = 123;
// 将函数 my_func 放在名为 "my_code_section" 的自定义段中
void my_func(void) __attribute__((section("my_code_section"))) {
// ...
}
int main(void) {
my_func();
return 0;
}
编译后,你可以使用 readelf -S your_program 查看段信息,会看到 my_data_section 和 my_code_section。
weak
作用:将一个符号(函数或变量)标记为“弱符号”。
- 弱函数:如果另一个文件中有一个同名的“强符号”(普通定义),则使用强符号,如果没有,则使用这个弱符号。
- 弱变量:与函数类似,如果强符号存在,则覆盖弱符号。
用途:
- 提供默认实现,允许用户覆盖,库可以提供一个默认的
log函数,用户可以提供自己的log函数来替代它。 - 在链接阶段解析未定义符号,避免链接错误。
示例:
文件 a.c (提供弱符号)
// 提供一个默认的、可被覆盖的 log 函数
void __attribute__((weak)) log_message(const char *msg) {
printf("Default log: %s\n", msg);
}
文件 main.c (使用并可能覆盖)
// extern 声明 log_message,此时它是一个弱符号
extern void log_message(const char *msg);
// main.c 也定义了一个同名的 log_message,它就是强符号,会覆盖 a.c 中的
void log_message(const char *msg) {
printf("Custom log: [%s]\n", msg);
}
int main(void) {
log_message("Hello, world!");
return 0;
}
编译链接 a.c 和 main.c,最终会调用 main.c 中定义的 log_message,如果只编译 a.c,则调用默认的。
如何检查属性是否生效?
你可以使用 -S 标志让 GCC 生成汇编代码,然后检查汇编输出,看看编译器是否根据你的 __attribute__ 做了相应的处理。
对于 noreturn:
gcc -S -O2 noreturn_example.c
然后打开 noreturn_example.s 文件,你会发现 my_exit 函数的结尾可能直接是 jmp exit 或类似的指令,而没有 ret (return) 指令。
对于 aligned:
gcc -S aligned_example.c
在汇编代码中,你可能会看到 .p2align 4 这样的指令,用于确保变量 var 的地址对齐到 16 字节。
__attribute__ 是 GCC/Clang 提供的一个非常强大的工具,它极大地增强了 C 语言的表达能力,虽然它不是标准 C,但在特定领域(尤其是需要与底层硬件或进行深度优化的场景)中,它几乎是不可或缺的。
核心要点:
- 它是编译器扩展,用于向编译器提供额外信息。
- 语法:
__attribute__((...))。 - 常用属性:
noreturn,aligned,packed,deprecated,format,const,section,weak等。 - 优点:优化代码、增强安全性、实现特殊功能(如自定义内存布局)。
- 可移植性:使用时需考虑,但可以通过宏进行条件编译来兼容其他编译器(如
#ifdef __GNUC__)。
