{
DO_GLOBAL_CTORS_BODY;
atexit (__do_global_dtors);
}
图5
结合图中的两个文件可以知晓,全局类变量的构造函数是通过__do_global_ctors()函数来调用的。从DO_GLOBAL_CTORS_BODY宏的实现来看,在11和12行获得数组中构造函数的个数,并在13和14行以逆序的方式调用每一个构造函数。__do_global_ctors()函数在最后调用C库的atexit()函数注册__do_gloabl_dtors()函数,使得程序退出时该函数得以被调用。
从__do_global_dtors()函数的实现来看,各全局变量的析构函数是顺序调用的,与调用构造函数的顺序是相反的。这就保证做到“先构造的全局类变量后析构。”
对__do_gloable_ctors()和__do_gloable_dtors()函数的调用是由C++语言的环境构建代码来调用的。总的说来,它们分别在进入和退出main()函数时被调用。
我们可以借助binutils工具集中的objdump来印证前面所述内容。图6示例了class1.o目标文件的反汇编代码。读者不需要细读其中的汇编代码,但请留意位置为4a和66的两个函数。前者是class1.cpp文件中s_class1变量的析构函数,后者则是对应的构造函数。
$ g++ -c –g class1.cpp
$ objdump -S -d --demangle=gnu-v3 class1.o
class1.o: file format pe-i386
Disassembly of section .text:
……内容有删减……
0000004a
4a: 55 push %ebp
4b: 89 e5 mov %esp,%ebp
4d: 83 ec 08 sub $0x8,%esp
50: c7 44 24 04 ff ff 00 movl $0xffff,0x4(%esp)
57: 00
58: c7 04 24 00 00 00 00 movl $0x0,(%esp)
5f: e8 9c ff ff ff call 0
64: c9 leave
65: c3 ret
00000066
66: 55 push %ebp
67: 89 e5 mov %esp,%ebp
69: 83 ec 08 sub $0x8,%esp
6c: c7 44 24 04 ff ff 00 movl $0xffff,0x4(%esp)
73: 00
74: c7 04 24 01 00 00 00 movl $0x1,(%esp)
7b: e8 80 ff ff ff call 0
80: c9 leave
81: c3 ret
82: 90 nop
83: 90 nop
图6
图7示例了如何通过objdump工具查看class1.o文件中.ctors和.dtors段中的内容。从内容中可以看到存在前面提到的4a和66两个值,而这两个值会最终被ld链接器分别放入__CTOR_LIST__和__DTOR_LIST__数组中。
$ objdump -s -j .ctors class1.o
class1.o: file format pe-i386
Contents of section .ctors:
0000 66000000 f...
$ objdump -s -j .dtors class1.o
class1.o: file format pe-i386
Contents of section .dtors:
0000 4a000000 J...
图7
了解了编译器是如何处理全局类对象的构造和析构函数后,我们就不难理解开始提到的有趣现象了。这是因为文件编译时的位置顺序会最终影响各类全局变量的构造与析构函数在__CTOR_LIST__和__DTOR_LIST__数组中的先后顺序。
了解这一内容有什么意义呢?这有助于我们掌握如何在C++中正确实现singleton设计模式,这一话题让我们留到另一篇博文中探讨。
本文出自 “李云” 博客