为何C语言API模块的析构函数未被调用?如何解决?
问题
将项目分为两个模块——包含main函数的example.c和存放所有API的api.c,二者分开编译,API生成的二进制文件可被example正常调用。
初始代码实现
example.c
__attribute__((constructor)) static void example_constructor() { printf("constructor: example\n"); api_setup(); } __attribute__((destructor)) static void example_destructor() { printf("destructor: example\n"); api_teardown(); } int main() { // make API calls }
api.c
__attribute__((constructor)) static void api_constructor() { printf("constructor: api\n"); } __attribute__((destructor)) static void api_destructor() { printf("destructor: api\n"); } void api_setup() { // malloc a buffer to be used in the API calls } void api_teardown() { // read + perform task on buffer. send to output file // free buffer } // other API definitions to manipulate the buffer
此时两个模块的构造、析构函数均能正常调用。但修改api.c,将初始化与清理逻辑移入自身的构造、析构函数后:
修改后的api.c代码
__attribute__((constructor)) static void api_constructor() { printf("constructor: api\n"); api_setup(); // 将初始化逻辑移到api.c的构造函数中 } __attribute__((destructor)) static void api_destructor() { printf("destructor: api\n"); api_teardown(); // 将清理逻辑移到api.c的析构函数中 } // other API definitions to manipulate the buffer (including setup/teardown)
出现异常:example.c和api.c的构造函数均能执行,但仅example.c的析构函数被调用。需要排查原因并实现让api.c的构造、析构函数全权负责初始化与清理的目标。
原因分析
核心问题在于静态函数的可见性与链接器的符号丢弃优化:
api_destructor是static修饰的静态函数,仅在api.c内部可见。- 当移除
example.c析构函数中对api_teardown的调用后,example.c不再显式依赖api.c的任何符号(构造函数由运行时自动调用,链接器不会将其视为显式依赖)。 - 链接器在优化阶段会判定
api_destructor无外部引用,将其从最终可执行文件中丢弃,导致运行时无法触发该析构函数。
构造函数能正常执行,是因为编译器会将构造函数注册到目标文件的特殊段(如.init_array),链接器默认保留这些段内容;但静态析构函数无显式依赖时,会被链接器优化丢弃。
解决方法
要让api.c的构造、析构函数全权负责初始化与清理,需确保链接器不丢弃api_destructor符号,以下是几种可行方案:
方案1:取消析构函数的static修饰
移除api_destructor的static关键字,使其成为全局可见符号:
__attribute__((destructor)) void api_destructor() { printf("destructor: api\n"); api_teardown(); }
链接器会识别到该全局符号,不会将其丢弃,运行时即可正常调用。
方案2:添加链接锚点函数
在api.c中定义一个空的全局函数,让example.c显式调用它,确保链接器认为api.c存在依赖:
api.c新增代码:
void api_anchor() {}
example.c的main函数新增调用:
int main() { api_anchor(); // 仅作为链接锚点,无实际业务逻辑 // make API calls }
这种方式会让链接器保留api.c的所有符号,包括析构函数。
方案3:编译时强制保留符号
使用GCC/Clang编译时,添加-u api_destructor选项,强制链接器保留该符号:
gcc -c api.c -o api.o gcc example.c api.o -u api_destructor -o example
该选项告知链接器,即便api_destructor无显式引用,也要将其保留在最终可执行文件中。
内容的提问来源于stack exchange,提问作者Kron
相关产品推荐
相关产品推荐

