在链接器脚本中,KEEP(*(.init_array))
和 KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*)))
这两条指令用于处理 C++ 全局对象的构造函数初始化,是保证 C++ 运行时正确初始化的关键部分。下面我将详细解释它们的用途和工作原理:
1. 基本作用
这两条指令共同确保:
- C++ 全局对象的构造函数被正确收集和执行
- 构造函数按照正确的优先级顺序执行
- 即使启用了
--gc-sections
优化,这些关键部分也不会被删除
2. 详细解析
KEEP(*(.init_array))
-
.init_array
是一个特殊的段,存放全局对象构造函数的指针数组 -
*(.init_array)
表示收集所有输入文件中的.init_array
段 -
KEEP()
确保即使这些符号看起来未被引用,也不会被链接器优化掉
KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*)))
-
.init_array.*
是带有优先级标记的子段 -
SORT_BY_INIT_PRIORITY
按优先级排序这些子段 - 这样确保构造函数按正确的顺序执行(优先级高的先执行)
3. C++ 全局对象初始化流程
- 编译器为每个全局对象生成构造函数调用
- 将这些调用按优先级放入
.init_array
或.init_array.NNN
段 - 链接器收集并排序这些段
- 运行时库按顺序调用这些构造函数
4. 典型链接器脚本示例
SECTIONS {
/* 其他段... */
.init_array :
{
PROVIDE_HIDDEN(__init_array_start = .);
KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN(__init_array_end = .);
} > FLASH
/* 其他段... */
}
5. 相关扩展
对应析构函数处理
同样需要处理全局对象的析构函数:
.fini_array :
{
PROVIDE_HIDDEN(__fini_array_start = .);
KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*)))
KEEP(*(.fini_array))
PROVIDE_HIDDEN(__fini_array_end = .);
} > FLASH
优先级标记
在代码中可以使用属性指定优先级:
__attribute__((constructor(101))) void my_init_func() {}
数字越小优先级越高(越早执行)
6. 常见问题解决
问题1:全局对象的构造函数未执行
- 检查链接脚本是否包含这些
KEEP
指令 - 检查是否使用了
--gc-sections
但没有保留这些段
问题2:构造函数执行顺序错误
- 确保使用了
SORT_BY_INIT_PRIORITY
- 检查各构造函数的优先级设置
问题3:在裸机环境中初始化失败
- 确保运行时库正确调用了这些初始化数组
- 检查内存区域设置是否正确
7. 底层原理
在 ELF 文件中,这些段实际上是函数指针数组:
.init_array : [地址] -> [函数指针1, 函数指针2, ...]
运行时库会遍历这个数组并依次调用每个函数。
通过这种机制,C++ 实现了全局对象的构造和析构的顺序控制,而链接器脚本中的这些 KEEP
指令确保了这套机制能可靠工作。