如何在C++中实现类似Linux内核MODULE_INIT的注册宏
问题原因
gold 链接器默认会启用段垃圾回收,没有被显式引用的自定义段会被当做无用内容丢弃,你当前的链接脚本没有标记段需要保留,也没有引用段内的内容,所以会出现段消失的问题。
可行解决方案
方案1:使用自定义段+修改链接脚本(可自主控制初始化时机)
第一步:修改链接脚本规则
首先通过命令导出当前架构的默认链接脚本,避免完全覆盖默认链接规则导致程序异常:
arm-oe-linux-gnueabi-ld --verbose > default.lds
打开导出的default.lds,在.text段定义的后面新增你自定义的mysec段,给段内容加上KEEP标记,强制链接器保留该段内容:
mysec : { __start_mysec = .; KEEP(*(mysec)) __end_mysec = .; }
第二步:实现MODULE_INIT宏
要给段内符号加used属性,防止编译阶段被优化,同时用extern "C"避免C++名字修饰导致符号引用失败:
#include <cstdint> // 初始化函数类型定义 typedef void (*module_init_func)(void); #define MODULE_INIT(func) \ extern "C" const module_init_func __init_##func \ __attribute__((used, section("mysec"))) = func
第三步:调用所有初始化函数
在你需要执行初始化的位置(比如main函数开头)遍历段内的函数指针挨个调用:
extern "C" module_init_func __start_mysec[]; extern "C" module_init_func __end_mysec[]; void run_all_module_init() { for (module_init_func* fn = __start_mysec; fn < __end_mysec; ++fn) { if (*fn != nullptr) { (*fn)(); } } }
第四步:编译链接参数
链接时指定修改后的链接脚本即可:
arm-oe-linux-gnueabi-g++ your_code.cpp -T default.lds -o output
方案2:使用系统默认.init_array段(无需修改链接脚本)
如果不需要自主控制初始化时机,希望和标准C++全局构造函数一样在main执行前自动运行,可以直接用系统默认的.init_array段,gold链接器默认会保留该段,无需修改链接脚本:
typedef void (*module_init_func)(void); #define MODULE_INIT(func) \ extern "C" const module_init_func __init_##func \ __attribute__((used, section(".init_array"))) = func
所有用该宏声明的函数会自动在main函数执行前被系统调用,不需要自己实现遍历逻辑。
注意事项
- 如果仍然出现段丢失的情况,可以在链接参数中加入
-Wl,--print-gc-sections,确认是否是链接器回收了你的自定义段 - 不要用仅包含自定义段的精简链接脚本,会覆盖所有默认的段规则,导致程序无法正常链接运行
内容的提问来源于stack exchange,提问作者Silver
相关产品推荐
相关产品推荐

