You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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的构造、析构函数全权负责初始化与清理的目标。

原因分析

核心问题在于静态函数的可见性与链接器的符号丢弃优化:

  1. api_destructor是static修饰的静态函数,仅在api.c内部可见。
  2. 当移除example.c析构函数中对api_teardown的调用后,example.c不再显式依赖api.c的任何符号(构造函数由运行时自动调用,链接器不会将其视为显式依赖)。
  3. 链接器在优化阶段会判定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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 19:57:31