__attribute__ 是什么?
__attribute__ 是 GCC(GNU Compiler Collection)和 Clang 等编译器提供的一个强大扩展,它允许开发者向编译器提供一些额外的信息或指令,从而改变代码的编译方式、链接方式或运行时行为。

- 语法:
__attribute__((attribute-list)) - 位置: 通常放在函数、变量、类型声明的末尾,前面用两个下划线
__,后面用两个下划线__。 - 注意: 因为它是编译器特有的扩展,所以不是标准的 C 语言,使用它可能会降低代码的可移植性,在 Linux/Unix 环境下的系统编程和底层开发中,它非常普遍。
__attribute__((constructor)) 和 __attribute__((destructor))
这是 __attribute__ 中最常用、也最神奇的两个属性之一,它们与程序的生命周期紧密相关。
__attribute__((constructor))
- 作用: 将一个函数标记为“构造函数”。
- 行为: 在
main()函数执行之前,所有被标记为constructor的函数会自动被调用。 - 执行顺序:
- 如果一个文件中有多个
constructor函数,它们的执行顺序与它们在源文件中出现的顺序相同。 - 如果不同文件中有多个
constructor函数,它们的执行顺序是不确定的(由链接器决定),为了保证执行顺序,通常的做法是将所有需要初始化的代码放在同一个文件中。
- 如果一个文件中有多个
__attribute__((destructor))
- 作用: 将一个函数标记为“析构函数”。
- 行为: 在
main()函数正常执行完毕后,或者在调用exit()函数后,所有被标记为destructor的函数会自动被调用。 - 执行顺序:
- 析构函数的调用顺序与构造函数的调用顺序相反,这符合“后进先出”(LIFO)的原则。
- 如果一个文件中有多个
destructor函数,它们的执行顺序与它们在源文件中出现的顺序相反。 - 不同文件间的
destructor函数执行顺序也是不确定的。
为什么需要它们?(典型应用场景)
constructor 和 destructor 主要用于实现自动化的初始化和清理工作,避免在 main 函数中写大量的“胶水代码”。
单例模式的静态初始化
假设你有一个全局唯一的资源(如数据库连接、配置管理器、日志系统),你希望在程序启动时就准备好它,而不是在第一次使用时才初始化(懒加载)。
#include <stdio.h>
#include <stdlib.h>
// 全局唯一的配置管理器实例
typedef struct {
int version;
char name[50];
} Config;
Config global_config;
// 被标记为 constructor 的初始化函数
void init_config() {
printf("Initializing global config...\n");
global_config.version = 1;
snprintf(global_config.name, sizeof(global_config.name), "MyApp");
}
// 获取配置的函数
void get_config() {
printf("Config: version=%d, name=%s\n", global_config.version, global_config.name);
}
int main() {
printf("main() function starts.\n");
get_config(); // 直接使用已经初始化好的配置
printf("main() function ends.\n");
return 0;
}
编译与运行:
gcc -o myapp myapp.c ./myapp
输出结果:
Initializing global config...
main() function starts.
Config: version=1, name=MyApp
main() function ends.
可以看到,init_config() 在 main() 之前就执行了,global_config 已经被正确初始化。
模块/库的初始化与清理
如果你在写一个动态库(.so 文件)或静态库,你可能希望库在被加载时自动注册某些功能(如插件、信号处理器),在卸载时自动注销。
#include <stdio.h>
#include <dlfcn.h> // 用于动态链接库
// 模块A的构造和析构函数
__attribute__((constructor))
void module_a_init() {
printf("[Module A] Initialized.\n");
// 在这里注册信号、钩子等
}
__attribute__((destructor))
void module_a_cleanup() {
printf("[Module A] Cleaning up...\n");
// 在这里注销信号、钩子等
}
// 模块B的构造和析构函数
__attribute__((constructor))
void module_b_init() {
printf("[Module B] Initialized.\n");
}
__attribute__((destructor))
void module_b_cleanup() {
printf("[Module B] Cleaning up...\n");
}
int main() {
printf("main() is running.\n");
return 0;
}
输出结果:
[Module A] Initialized.
[Module B] Initialized.
main() is running.
[Module B] Cleaning up...
[Module A] Cleaning up...
注意构造和析构的顺序。module_a 和 module_b 的构造顺序取决于它们在源文件中的位置(a 在 b 前),析构顺序则相反。
高级用法:优先级
构造函数之间有依赖关系,模块 B 依赖于模块 A 的初始化,为了控制执行顺序,GCC 提供了优先级参数。
-
constructor (priority=N):- 优先级
N是一个整数。 - 数字越小,优先级越高,越早执行。
- 默认优先级是
65535(数值最大,优先级最低)。 - 优先级为
0的函数会最先执行。
- 优先级
-
destructor (priority=N):- 逻辑与构造函数相反。
- 数字越小,优先级越高,越晚执行(因为析构是 LIFO)。
示例:
#include <stdio.h>
// 优先级为 0,最先初始化
__attribute__((constructor(0)))
void first_init() {
printf("First init (priority 0)\n");
}
// 默认优先级 (65535),最后初始化
__attribute__((constructor))
void last_init() {
printf("Last init (default priority)\n");
}
// 优先级为 100,在默认优先级之前,但在 0 之后
__attribute__((constructor(100)))
void middle_init() {
printf("Middle init (priority 100)\n");
}
int main() {
printf("main() is running.\n");
return 0;
}
输出结果:
First init (priority 0)
Middle init (priority 100)
Last init (default priority)
main() is running.
对于析构函数,顺序会反过来。
跨平台兼容性
这是使用 __attribute__ 最大的注意事项。
- GCC / Clang / ICC (Intel): 完全支持
constructor和destructor。 - MSVC (Visual Studio): 不支持
__attribute__((...))语法。
如果你需要编写跨平台的代码,可以使用宏来封装它们。
#include <stdio.h>
// 跨平台宏定义
#if defined(__GNUC__) || defined(__clang__)
#define CONSTRUCTOR __attribute__((constructor))
#define DESTRUCTOR __attribute__((destructor))
#else
// 对于不支持的平台,我们只能放弃这个特性
// 或者提供替代方案,比如手动调用一个 init() 函数
#define CONSTRUCTOR
#define DESTRUCTOR
// 并在文档中说明,需要手动调用 init() 和 cleanup()
#endif
CONSTRUCTOR
void my_auto_init() {
printf("This will run before main() on GCC/Clang.\n");
}
DESTRUCTOR
void my_auto_cleanup() {
printf("This will run after main() on GCC/Clang.\n");
}
int main() {
printf("main() function.\n");
return 0;
}
在 MSVC 中编译这段代码,my_auto_init 和 my_auto_cleanup 不会被自动调用,它们只是普通的函数,开发者需要遵守约定,在程序入口处手动调用它们。
| 特性 | __attribute__((constructor)) |
__attribute__((destructor)) |
|---|---|---|
| 触发时机 | main() 函数执行之前 |
main() 函数执行完毕后或调用 exit() 时 |
| 核心用途 | 自动化初始化(单例、模块注册、全局状态设置) | 自动化清理(资源释放、注销、状态重置) |
| 执行顺序 | 同文件内按声明顺序;跨文件不确定 | 同文件内与声明顺序相反;跨文件不确定 |
| 高级控制 | constructor(priority=N),N越小越先执行 |
destructor(priority=N),N越小越晚执行 |
| 可移植性 | 差,是 GCC/Clang 扩展,MSVC 不支持 | 差,同上 |
| 注意事项 | 过度使用会使程序逻辑难以追踪,应谨慎使用 | 确保析构函数是幂等的,可以被多次安全调用 |
__attribute__((constructor)) 和 __attribute__((destructor)) 是 C 语言中一个非常底层的“黑科技”,它能让你的代码在特定场景下(如库开发、系统工具)变得非常优雅和强大,但务必记住它的平台局限性,并谨慎使用以避免引入难以调试的问题。
