本文最后更新于: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

      过程如图所示

      .ctor段的形成

  • 全局变量构造源码剖析

    • 入口函数_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 协议 ,转载请注明出处!

八大排序算法总结 上一篇
设计模式——单例模式 下一篇