本文最后更新于:2020年6月30日 晚上
* 前一阵儿,被施老师问到C++全局变量是如何构造和析构的。。。?之前看书的时候好像忽略了这个点。。现在补上。。。→_→ *
理解这个点之前,需要把main函数的前世今生搞清楚。。请戳传送门
Glibc中的文件类型
在Glibc中主要分为头文件和库文件
头文件位于/usr/include
库文件包含动态库和静态库
动态库位于/lib/libc.so.6
静态库位于/usr/lib/libc.a
除了C标准库外,Glibc中还有几个辅助的“运行库”
/usr/lib/crt1.o 中包含程序的入口函数_start,由它负责调用__libc_start_main完成初始化,并调用main函数
/usr/lib/ctri.o
/usr/lib/ctrn.o
Glibc中的crti.o和crtn.o的内容和作用
因为全局变量必须在main函数之前构造、必须在main函数之后析构,所以运行库在每个目标文件中引入了两个初始化相关的段.init和.finit
链接器在进行链接时,会把所有输入的目标文件中的.init和.finit段按顺序收集合并成最终输出文件中的.init和.finit,这两个段实际上分别包含_init() 和 _finit()函数
crti.o和crtn.o中的代码实际上是_ init() 和_ finit()函数的开始和结尾部分,它们辅助.init 和 .finit中的指令实现初始化函数。crti.o和crtn.o与其他目标文件按顺序链接后,形成完整的_ init() 和_ finit()函数
必须保证在链接时,crti.o在目标文件和系统库之前,crtn.o在目标文件和系统调用之后。因此链接器的输入文件顺序如下(ctr1.o不包含.init和.finit段,所以不会影响生成段的顺序)
ld ctr1.o ctri.o [user_object]_[system_libraries] ctrn.o
GCC中的crtbegin.o和crtend.o的内容和作用
C++语言的实现和编译器密切相关,所以GCC才是C++全局构造和析构的真正实现者,crtbegin.o和crtend.o配合Glibc实现C++全局构造和析构
crti.o和crtn.o中的.init和.finit提供一个在main函数之前和之后的代码运行机制,真正的全局构造和析构由crtbegin.o和crtend.o实现
对于每个编译单元,GCC编译器会遍历其所有的全局对象,生成一个特殊的函数,这个特殊的函数负责初始化本单元中的所有全局对象
目标文件中有了这个函数,编译器就会在这个目标文件中产生.ctors段,.ctors段中放置一个指向特殊函数的指针
链接时,链接器将每个目标文件的.ctors段合并成一个.ctors段,因此合并后的.ctors段就成为了一个函数指针数组,每个元素都指向一个目标文件的特殊函数
链接时,各个目标文件的前后还要链接crtbegin.o和crtend.o,这两个目标文件具有的.ctors段也会被合并到可执行文件中
crtbegin.o作为.ctors的开头部分,其.ctors段中存储了一个4字节的0xFFFFFFFF(-1),链接器负责将这个数字改成所有目标文件中的特殊函数的总数量,还将这个段的起始地址定义成符号 _ _ CTOR_ LIST _ _ ,即_ _ CTOR_LIST _ _就代表合并后的.ctors的起始地址
crtend.o中的内容只有一个0,定义了一个_ _ CTOR_ END_ _符号,指向.ctor段的末尾
实际链接的目标文件顺序如下
ld ctr1.o ctri.o ctrbegin.o a.o b.o ctrend.o ctrn.o
过程如图所示
全局变量构造源码剖析
入口函数_start中调用了_libc_start_main函数,_libc_start_main函数传入参数_libc_csu_init函数指针
//glibc-2.6.1/csu/Elf-init.c void __libc_csu_init (int argc, char **argv, char **envp) { ...... _init (); const size_t size = __init_array_end - __init_array_start; for (size_t i = 0; i < size; i++) (*__init_array_start [i]) (argc, argv, envp); }
_ _ libc_ csu_ init函数中调用了.init段中的_ init函数,_ init函数中又调用了_ _ do_ global_ ctors_ aux函数,这个_ _ do_ global_ ctors_aux函数来自GCC中的目标文件crtbegin.o
//Crtstuff.c static void __attribute__((used)) __do_global_ctors_aux (void) { func_ptr *p; for (p = __CTOR_END__ - 1; *p != (func_ptr) -1; p--) (*p) (); }
_ _ do_ global_ ctors_aux函数将合并的.ctors段中存储的各个目标文件的特殊函数依次调用,即完成了全局变量的构造。
全局变量析构源码剖析
一直说的特殊函数是什么?特殊函数除了完成对象构造,还干了些什么?
//特殊函数的伪代码 static void GLOBAL_I_A(void) { A::A();//调用构造函数 __cxa_exit(_tcf_1);//_tcf_1函数被注册到main函数结束后调用 }
__tcf_1函数中有什么东西?
//此函数名由编译器生成 static void __tcf_1(void) { A.~A();//调用析构函数 }
由此可见在特殊函数中不仅调用了构造函数,而且还一道注册了析构函数。注册的析构函数与调用构造函数的顺序刚好相反,符合先构造后析构的原理
* 今天真热。。。41℃。。。→_→ *
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!