为了相对透彻的了解C程序运行期的初始化顺序,首先介绍一些名词定义。用过Java的同志都知道, Java是一种跨平台语言。真的是所有的平台都能自如的运行Java程序吗?当然不可能。运行它的前提是你需要安装Java Run-time(JRE)。C语言也需要自己的运行期类库,windows系统正好支持这种类库,所以C++程序可以顺利的在windows系统上运行了。这个类库叫C Run-time(CRT), CRT这个名词可能并不陌生,大家在很多地方都看见过它。它建立了C程序运行环境。
- 有些同志可能想过这样的问题。C程序怎么找到main函数的呢?其实是CRT执行了你的main函数或者WinMain函数的。我们在VC IDE的选项中可以填入Entry-point symbol,如wWinMainCRTStartup。这就是给CRT定义了一个入口宏,根据这个宏CRT会运行相应类型的Main函数。由于这里我们讨论初始化顺序,所以main的具体调用过程不做介绍了。 现在我们整理一下C程序的启动过程。在你开始执行一个C程序的时候, CRT库会根据不同的入口宏调用相应的CRT初始化函数,在这个函数中会做一些CRT运行一系列准备工作,按次序为:获得win32版本、对堆进行初始化、初始化操作系统接口、获得命令行参数、初始化stdio接口等等操作。
- .
- . . 之后的部分就是我们关心的东西了,CRT会根据函数指针表对我们定义的静态变量进行初始化。
.static void _CALLTYPE4 _initterm (
- PFV * pfbegin,
- PFV * pfend
- )
.#else /* _M_MPPC */ .void _CALLTYPE4 _initterm (
- PFV * pfbegin,
- PFV * pfend
- )
.#endif /* _M_MPPC */ .{
- /*
- * walk the table of function pointers from the top down, until
- * bottom is encountered. Do not skip the first entry.
- */
-
for ( ;pfbegin < pfend ; pfbegin++)
- {
- /*
- * if current table entry is non-NULL (and not -1), call
- * thru it from end of table to begin.
- */
-
if ( *pfbegin != NULL && *pfbegin != (PFV) -1 )
- (**pfbegin)();
- }
.}
静态变量进行初始化顺序是基类的静态变量先初始化,然后是它的派生类。直到所有的静态变量都被初始化。这里需要注意全局变量和静态变量的初始化是不分次序的。这也不难理解,其实静态变量和全局变量都被放在公共内存区。可以把静态变量理解为带有“作用域”的全局变量。在一切初始化工作结束后,main函数会被调用,如果某个类的构造函数被执行,那么首先基类的成员变量会被初始化。
要请注意的是,成员变量的初始化次序只与定义成员变量的顺序有关,与构造函数中初始化列表的顺序无关。因为成员变量的初始化次序是根据变量在内存中次序有关,而内存中的排列顺序早在编译期就根据变量的定义次序决定了。这点在EffectiveC++中有详细介绍。
- bbb的成员变量定义:
- private:
- int n1;
- int n2;
- bbb的构造函数:
- bbb::bbb()
- :n2(1),
- n1(2)
- {
- }
- 汇编代码:
- 00401535 mov eax,dword ptr [ebp-4]
- 00401538 mov dword ptr [eax+4],2
- 0040153F mov ecx,dword ptr [ebp-4]
- 00401542 mov dword ptr [ecx+8],1
- 然后依照派生链初始化派生类的成员函数。
- .
- . 那么变量的初始化顺序就应该是:
- 1基类的静态变量或全局变量
- 2派生类的静态变量或全局变量
- 3基类的成员变量
- 4派生类的成员变量