KEEP(*(.init_array)) 和 KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*)))

在链接器脚本中,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++ 全局对象初始化流程

  1. 编译器为每个全局对象生成构造函数调用
  2. 将这些调用按优先级放入 .init_array.init_array.NNN
  3. 链接器收集并排序这些段
  4. 运行时库按顺序调用这些构造函数

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 指令确保了这套机制能可靠工作。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容