今天小翻了下新书《程序员的自我修养——链接、装载与库》中的11.4.2章节的《MSVC CRT的全局构造与析构》部分。整体而言,作者对于全局函数的调用时机阐述得比较清楚,但其中有一点疑问,作者并没有写清楚,这里我就补充下。(以下讨论的是Windows平台,linux类似。) 我们知道,编译完的控制台exe文件一般情况下并不是从main函数执行的,也就是说pe文件头的入口点并不是指向main函数的,而是指向一个叫做mainCRTStartup的启动函数。该函数在vc安装目录的crt/src/crtexe.c中实现。具体如下: void mainCRTStartup(void) { ...... _initterm( __xi_a, __xi_z ); //这个函数进行全局对象构造函数的初始化调用代码 ...... _initterm( __xc_a, __xc_z ); ...... } 其中,_initterm( __xi_a, __xi_z ); 是全局对象构造函数的初始化调用代码。我们来看下它的具体实现: void __cdecl _initterm ( _PVFV * pfbegin,_PVFV * pfend) { while ( pfbegin < pfend ) { if ( *pfbegin != NULL ) (**pfbegin)(); ++pfbegin; } } 从上面的代码中我们可以看到,全局对象的初始化函数被调用为void fn(void)类型。这就有个疑问了,看下面代码:#include <stdio.h> void gfn (); class A { int a; public: A (char* a); }; A::A(char* a) { printf ("global!/n"); } A a("sapair"); int main () { printf ("main!/n"); } 上面这段代码全局对象构造函数并不是无参类型,那按照_initterm 的实现,我们知道_initterm 调用的初始化函数是无参类型的,那A的有参构造函数又是如何在main函数之前被调用的呢?下面我们来解密这个问题: 用OD载入编译后的代码,可以找到下面的反汇编片段: 004010B0 >/$ 55 push ebp 004010B1 |. 8BEC mov ebp, esp 004010B3 |. 83EC 40 sub esp, 40 004010B6 |. 53 push ebx 004010B7 |. 56 push esi 004010B8 |. 57 push edi 004010B9 |. 8D7D C0 lea edi, dword ptr [ebp-40] 004010BC |. B9 10000000 mov ecx, 10 004010C1 |. B8 CCCCCCCC mov eax, CCCCCCCC 004010C6 |. F3:AB rep stos dword ptr es:[edi] 004010C8 |. 68 28204200 push 00422028 ; ASCII "sapair" 004010CD |? B9 587D4200 mov ecx, offset a 004010D2 |? E8 2EFFFFFF call 00401005 ;就是A的有参构造函数 004010D7 |. 5F pop edi 004010D8 |? 5E pop esi 004010D9 |? 5B pop ebx 004010DA |. 83C4 40 add esp, 40 004010DD |? 3BEC cmp ebp, esp 004010DF |? E8 EC000000 call _chkesp 004010E4 /. 8BE5 mov esp, ebp 004010E6 5D pop ebp 004010E7 C3 retn 此时观察堆栈窗口,有调用回溯,我们可以转到调用代码处: 00402F90 >/$ 55 push ebp 00402F91 |. 8BEC mov ebp, esp 00402F93 |> 8B45 08 mov eax, dword ptr [ebp+8] 00402F96 |. 3B45 0C cmp eax, dword ptr [ebp+C] 00402F99 |. 73 18 jnb short 00402FB3 00402F9B |. 8B4D 08 mov ecx, dword ptr [ebp+8] 00402F9E |. 8339 00 cmp dword ptr [ecx], 0 00402FA1 |. 74 05 je short 00402FA8 00402FA3 |. 8B55 08 mov edx, dword ptr [ebp+8] 00402FA6 |. FF12 call dword ptr [edx] 00402FA8 |> 8B45 08 mov eax, dword ptr [ebp+8] 00402FAB |. 83C0 04 add eax, 4 00402FAE |. 8945 08 mov dword ptr [ebp+8], eax 00402FB1 |.^ EB E0 jmp short 00402F93 00402FB3 |> 5D pop ebp 00402FB4 /. C3 retn 上面这段反汇编代码是不是有点眼熟?呵呵,不错的,这段反汇编就是_initterm( __xi_a, __xi_z )的实现:) 由此,我们总结了下原因: 原来,编译器对于全局对象的非无参构造函数进行了一个包装,把对有参构造函数的调用进行了一个封装,封装为一个无参类型的函数,然后把这个无参类型的函数作为_initterm函数的调用。
全局对象构造函数的调用时机
最新推荐文章于 2024-07-08 19:25:51 发布