Appearance
.init / .fini / .init_array / .fini_array —— 初始化与终止节
C++ 全局对象的构造在
main之前,析构在main之后——但.text里没有对应的call指令。这些"藏在背后的代码"是怎么被调用的?答案在.init、.fini和init_array、.fini_array里。它们是 ELF 定义的初始化/终止钩子机制:.init/.fini是传统方式(放函数代码),init_array/fini_array是函数指针数组(更现代、更灵活)。本篇讲透这四节的结构、调用时序、以及它们如何实现"全局对象先于 main 构造"。
更新时间:2026-08-06
一、术语前置
| 术语 | 含义 |
|---|---|
.init | 初始化代码节——存放 _init 函数,由动态链接器在 main 之前调用 |
.fini | 终止代码节——存放 _fini 函数,由 __cxa_finalize / atexit 机制在 main 之后调用 |
.init_array | 初始化函数指针数组——存放构造函数指针、全局对象初始化函数的地址 |
.fini_array | 终止函数指针数组——存放析构函数指针 |
.preinit_array | 最先执行的初始化数组——在 .init_array 之前,通常给 libc 内部的早期初始化用 |
.ctors / .dtors | 旧的构造/析构列表(GCC 遗留,现代用 .init_array) |
__libc_csu_init | glibc 的 C 启动函数,遍历 .init_array 并调用每个条目 |
_start → __libc_start_main → main | 标准启动链,.init_array 在这条链的 main 之前触发 |
二、调用时序全景图

退出时的对称流程(__libc_csu_fini → .fini_array → .fini/_fini)原理相同,方向相反。
三、.init / .fini —— 传统方式
.init 里放的是 _init 函数的机器码:
bash
$ readelf -S ./cpu_demo | grep '\.init'
[10] .init PROGBITS 0000000000401608 00001608
0000001a AX 0 0 4用 objdump -d 能看到它做了什么:
asm
Disassembly of section .init:
401608: sub $0x8,%rsp
40160c: call 401520 <__gmon_start__@plt>
401611: add $0x8,%rsp
401615: ret这是 GCC 自动生成的,主要作用是调 __gmon_start__(gprof 性能分析的初始化钩子)。
.fini 同理,放 _fini 函数,在程序退出前执行清理。
.init/.fini只能放一个函数(各只有一段代码),灵活性有限。因此现代 GCC 主要用下面的_array系列。
四、.init_array / .fini_array —— 函数指针数组
4.1 结构
.init_array 不是一段代码,而是一个函数指针数组(void (*)(void) 的集合)。启动流程中,__libc_csu_init 逐项取出函数指针并 call。
bash
$ readelf -S ./cpu_demo | grep init_array
[19] .init_array INIT_ARRAY 0000000000607dc8 00007dc8
00000008 WA 0 0 8| 字段 | 值 | 含义 |
|---|---|---|
Size | 0x8(8 字节) | 一个函数指针(64 位) |
Flags | WA | 可读可写 |
EntSize | 0 | 数组类型无固定条目大小 |
bash
# 查看 .init_array 里的函数指针
$ objdump -s -j .init_array ./cpu_demo
Contents of section .init_array:
607dc8 7a1b4000 00000000 ... → 地址 0x401b7a(一个构造函数的入口)可以用 addr2line -e ./cpu_demo 0x401b7a 反查是哪个函数。
4.2 什么代码会进 .init_array
| 场景 | 生成的节 |
|---|---|
MyClass obj;(全局对象) | .init_array + .fini_array |
__attribute__((constructor)) void init() {} | .init_array |
__attribute__((destructor)) void fini() {} | .fini_array |
| C++ 全局/静态对象构造函数 | .init_array |
| C++ 全局/静态对象析构函数(通过 atexit 注册) | .fini_array |
c
// 这段代码的 init_hook 会在 main 之前自动执行
__attribute__((constructor))
void init_hook(void) {
printf("before main\n");
}
__attribute__((destructor))
void fini_hook(void) {
printf("after main\n");
}4.3 优先级控制
GCC 允许给 constructor/destructor 指定优先级(数字越小越先执行):
c
__attribute__((constructor(101))) void init_first() {} // 先
__attribute__((constructor(102))) void init_second() {} // 后编译器按优先级分组,同优先级按编译顺序。通过 readelf -x .init_array 可以看到指针在数组里的排列顺序。
五、.preinit_array —— 最早执行的初始化
.preinit_array 在 .init_array 之前执行。它通常只有 libc 的内部代码用——比如初始化 locale、线程本地存储等。应用代码一般不进这个节。
bash
$ readelf -S ./cpu_demo | grep preinit
[18] .preinit_array PREINIT_ARRAY 0000000000607dc0 00007dc0
00000008 WA 0 0 8六、.ctors / .dtors —— 历史遗留
GCC 早期版本用 .ctors 和 .dtors 代替 _array 系列。它们是用特殊头尾哨兵括起来的链表:
bash
.ctors 布局:
┌──────────────┐
│ 0xffffffff │ ← 头部哨兵
├──────────────┤
│ func_ptr[0] │ ← 构造函数指针(按优先级倒序)
├──────────────┤
│ func_ptr[1] │
├──────────────┤
│ 0x00000000 │ ← 尾部哨兵
└──────────────┘现代 GCC 默认用 .init_array(更简单、不需要遍历链表),.ctors / .dtors 仅在某些 -fno-use-init-array 场景或老旧代码里出现。
七、调用链完整时序
bash
execve("app")
→ 内核加载 ELF → mmap LOAD 段
→ 加载 ld.so → ld.so 加载依赖 .so
→ ld.so 调各 .so 的 .init/.init_array
→ ld.so 跳到程序 Entry point (_start)
→ __libc_start_main
→ __libc_csu_init
→ ① .preinit_array[] (libc 早期初始化)
→ ② _init() (调 __gmon_start__ 等)
→ ③ .init_array[] (全局对象构造、constructor 函数)
→ main() ★ 你的代码
→ exit()
→ .fini_array[] (析构函数、destructor 函数)
→ _fini() (清理)八、实操速查
bash
# 看初始化节
readelf -S ./app | grep -E 'init|fini|ctors|dtors'
# 看 .init_array 里的函数指针
objdump -s -j .init_array ./app
# 看 _init 的反汇编
objdump -d -j .init ./app
# 反查指针指向哪个函数
addr2line -e ./app 0x401b7a
# 查看 DT_INIT / DT_FINI 指向(.dynamic 中)
readelf -d ./app | grep -E 'INIT|FINI'
# 验证 constructor 优先级
g++ -o test test.cpp && readelf -x .init_array test九、与相关文档的关系
| 文档 | 覆盖内容 | 本文的关系 |
|---|---|---|
| elf-format.md §四 | 常见 Section 简要列表 | 本文是初始化终止节的专题深入 |
| plt-got.md | .dynamic 中的 DT_INIT/DT_FINI 指向 | 本文解释这两个 tag 指向的节里有什么 |
| compile-link-load.md | _start→main 启动链 | 本文把这条链上 init/fini 环节的 ELF 结构讲透 |
| c++-abi.md | C++ 全局对象构造/析构的 ABI | 本文从 ELF section 视角看"在哪存函数指针" |
一句话总结:
.init和.fini是各一段函数代码(传统),.init_array和.fini_array是函数指针数组(现代)。__libc_csu_init在main前按.preinit_array → _init → .init_array的顺序逐个调用,实现了"全局对象先于 main 构造"的 C++ 语义。__attribute__((constructor/destructor))的背后就是把函数指针写入这两个数组。