C++ 程序中,全局对象的初始化是在主函数([w]main() or [w]WinMain)被调用之前被初始化的。
Visual studio 编译器在编译时会判断编译选项 "-subsystem" 来指点输出程序是 "console" 还是 "windows",同时还会判断程序中所使用的字符集是否是 unicode 的。 所以 visaul studio 的程序会有四个不同的程序入口 (entry)。
subsystem\Unicode | Unicode(yes) | Non-unicode |
"console" | wmainCRTStartup(wmain) | mainCRTStartup (main) |
"windows" | wWinMainCRTStartup(wWinMain) | WinMainCRTStartup(WinMain) |
通常在通过向导创建的程序的入口是 _tmain ,这是采用 TCHAR 封装之后的版本, _tmain 对于两个不同的函数定义,根据当前是否是unicode。
#define _tmain wmain
#define _tmain main
看 mainCRTStartup 的代码,可知道全局对象的初始化是通过调用 _initterm 函数完成的,这个函数在 doexit 中还会被调用。 这个函数的输入是函数指针的首地址和末地址。 这个函数只是简单的调用传递进去的函数指针。
在对象被创建的时候,会传递创建对象的函数指针,在函数被销毁的适合,会传递对象析构的函数指针给 _initterm 函数。
如果一个 C++ 的全局对象有构造函数的话,那么这个对象在声明的时候,会被转化成为一段代码。 我写了一个简单的程序来测试:
#include <iostream> using namespace std; class Test { public: Test() { int i, j ; for(i = 1 ; i < 9 ; i ++ ) { for( j = 1 ; j <= i ; j ++ ) { cout << " " << i << " x " << j << " = " << i * j ; } cout << endl; } } }; Test objTest; int _tmain(int argc, _TCHAR* argv[]) { return 0; }在代码 Test objTest 处,由于 Test 对象有构造函数,那么声明的全局对象 objTest 会被转换成为一段代码:
push ebp mov ebp,esp sub esp,0C0h push ebx push esi push edi lea edi,[ebp-0C0h] mov ecx,30h mov eax,0CCCCCCCCh //这段代码是在debug 版下为了防止stack 溢出所添加的调试代码 rep stos dword ptr es:[edi] mov ecx,offset objTest (41A148h) //对象的 this 指针是通过 ecx 传递的 call Test::Test (4111D1h) //调用构造函数 pop edi pop esi pop ebx add esp,0C0h cmp ebp,esp call @ILT+425(__RTC_CheckEsp) (4111AEh) // Visual studio 好像是从 2005 之后,引入了 RTC (Runtime check ) mov esp,ebp pop ebp ret这一段代码会被存放在程序的代码段中,在程序被加载之后,会初始化成很多函数指针,在 mainCRTStartup 被调用的时候,会把这些函数指针传递给 _initterm 函数。 _initterm 函数会在 main 函数调用之前被调用,在全局对象初始化结束之后, main 函数会被调到。 上面的测试中,在 Test::Test 执行之后,main 函数会被调用执行。
上面所做的分析是编译器相关的。