C++需要单独内存段是因为全局对象构造/析构、RTTI、异常处理等机制依赖特定段布局,若混入.data或.rodata,链接器无法识别初始化顺序,导致构造函数不执行或dynamic_cast崩溃。

为什么C++需要单独的内存段而不是复用C的
C++的全局对象构造函数(_GLOBAL__sub_I_*)和析构函数(__cxa_atexit注册项)、RTTI、异常表(.eh_frame)等都依赖特定段布局。若把它们混进.data或.rodata,链接器可能无法识别初始化顺序,导致构造函数不执行,或dynamic_cast崩溃。GCC/Clang默认把C++运行时元数据放在.init_array、.fini_array、.eh_frame等标准段——这些段名本身已是“专门段”,但你可能需要自定义其地址、对齐或合并策略。
如何在.ld中显式声明并定位C++关键段
链接脚本不“为C++定义新段”,而是**接管编译器生成的段名**,控制其位置、属性与填充。重点不是造名字,而是收编已有段:
-
.init_array和.fini_array必须放在可读可写、且被C库启动代码扫描的区域(通常紧接.data之后),否则全局对象不构造 -
.eh_frame需保持只读(READONLY),且不能被strip掉;某些嵌入式平台要求它连续存放,需用KEEP防止优化移除 -
.rodata若包含vtable,要确保4字节对齐(ALIGN(4)),否则dynamic_cast可能读错虚表指针 - 使用
PROVIDE暴露C++运行时符号,例如PROVIDE(__init_array_start = .);供__libc_init_array调用
示例片段:
SECTIONS
{
. = ALIGN(4);
.rodata : {
*(.rodata)
*(.rodata.*)
*(.eh_frame) /* 收编异常信息 */
} > FLASH
<p>.data : {
<em>(.data)
</em>(.data.<em>)
. = ALIGN(4);
__init_array_start = .;
</em>(.init_array)
__init_array_end = .;
} > RAM
}常见错误:段名拼错、遗漏KEEP、忽略段属性
最常踩的坑是以为自己“定义了新段”,结果链接器根本没匹配到目标段:
立即学习“C++免费学习笔记(深入)”;
- 写成
.cpp_init而非.init_array→ 编译器生成的构造函数列表被丢弃,_GLOBAL__sub_I_*全失效 - 漏掉
KEEP(*( .init_array ))→ LTO或-ffunction-sections下,链接器认为该段未被引用而删除 - 把
.eh_frame放进NOLOAD段 → 异常抛出时找不到栈展开信息,直接abort - 未给
.init_array设ADDR(.data) + SIZEOF(.data)偏移 → 启动代码扫描范围外,构造函数跳过
嵌入式场景下必须检查的兼容点
裸机或RTOS环境没有glibc,C++运行时行为更敏感:
- 确认你的C runtime(如newlib、picolibc)是否提供
__libc_init_array;若无,得手写循环调用.init_array里的函数指针 - ARM Cortex-M系列要求
.init_array地址按字对齐,且每个条目是4字节函数指针;若目标文件由Clang生成,可能含8字节条目(需--fix-cortex-m-3或手动过滤) - 某些MCU Flash控制器禁止在运行时从Flash执行
.init_array中的代码(因XN位限制),此时必须复制到RAM再调用
真正麻烦的从来不是语法——是搞清你用的toolchain、C++标准库、启动代码三方之间,到底谁在读哪个段、按什么格式读、有没有隐式依赖。


















