1、 在介紹靜態(tài)對(duì)象、全局對(duì)象與程序的運(yùn)行機(jī)制之間的關(guān)系之前,我們首先看一下atexit函數(shù)。
atexit函數(shù)的聲明為:int atexit( void ( __cdecl *func )( void ) );
參數(shù)為函數(shù)指針,返回值為整型,0表示成功,其他表示失敗。當(dāng)程序運(yùn)行結(jié)束時(shí),他調(diào)用atexit函數(shù)注冊(cè)的所有函數(shù)。如果多次調(diào)用atexit函數(shù),那么系統(tǒng)將以LIFO(last-in-first-out)的方式調(diào)用所有的注冊(cè)函數(shù)。
舉例如下(代碼摘自MSDN):
1
#include <stdlib.h>
2
#include <stdio.h>
3
4
void fn1( void ), fn2( void ), fn3( void ), fn4( void );
5
6
void main( void )
7

{
8
atexit( fn1 );
9
atexit( fn2 );
10
atexit( fn3 );
11
atexit( fn4 );
12
printf( "This is executed first.\n" );
13
}
14
15
void fn1()
16

{
17
printf( "next.\n" );
18
}
19
20
void fn2()
21

{
22
printf( "executed " );
23
}
24
25
void fn3()
26

{
27
printf( "is " );
28
}
29
30
void fn4()
31

{
32
printf( "This " );
33
}
34
編譯、運(yùn)行程序后,程序的輸出為:
This is executed first.
This is executed next.
注冊(cè)函數(shù)的順序?yàn)椋篺n1、fn2、fn3、fn4,但是調(diào)用順序?yàn)閒n4、fn3、fn2、fn1。
2、理解了atexit函數(shù)之后,我們就可以來看看局部靜態(tài)對(duì)象了。
1 class AAA{ … } ;
2 AAA* createAAA()
3 {
4 static AAA a ;
5 return &a ;
6 }
7
在調(diào)試狀態(tài)下,匯編代碼如下(請(qǐng)觀察藍(lán)色標(biāo)記出來的代碼):
AAA* createAAA()
{
…
static AAA a ;
…
[1] 00401056 call AAA::AAA (4010A0h)
[2] 0040105B push offset `createAAA'::`2'::a::`dynamic atexit destructor' (442410h)
[3] 00401060 call atexit (409A50h)
00401065 add esp,4
00401068 mov dword ptr [ebp-4],0FFFFFFFFh
return &a ;
0040106F mov eax,offset a (452620h)
}
…
00401091 ret
注:[1]、[2]、[3]為方便說明加入的字符,實(shí)際代碼中并不存在。
[1]語句很明顯的調(diào)用AAA的構(gòu)造函數(shù)。
[2]語句將442410h壓入棧中。
[3]語句調(diào)用atexit函數(shù),根據(jù)我們的了解,atexit的參數(shù)應(yīng)該是函數(shù)指針。那么我們來分析一下442410h處的代碼,從注釋來看,我們看到了destructor。代碼如下:
`createAAA'::`2'::a::`dynamic atexit destructor':
…
[1] 0044242E mov ecx,offset a (452620h)
[2] 00442433 call AAA::~AAA (403A90h)
…
0044244B ret
[1]語句將a的地址放在ecx寄存器中,這是this調(diào)用規(guī)范的風(fēng)格。
[2]語句調(diào)用AAA的析構(gòu)函數(shù)。
程序結(jié)束時(shí),將調(diào)用atexit函數(shù)注冊(cè)的442410h處的函數(shù),進(jìn)而調(diào)用了AAA的析構(gòu)函數(shù)。從而保證了析構(gòu)函數(shù)的調(diào)用。
3、 了解了局部靜態(tài)對(duì)象之后,我們來看看全局對(duì)象。
我們知道全局對(duì)象必須在main函數(shù)前已經(jīng)被構(gòu)造。為了弄清楚全局對(duì)象何時(shí)被構(gòu)造,我在全局對(duì)象的實(shí)例化處設(shè)置了斷點(diǎn),調(diào)用堆棧如下:
static.exe!aaaa::`dynamic initializer'() Line 22 C++
static.exe!_initterm(void (void)* * pfbegin=0x00451038, void (void)* * pfend=0x00451064) Line 707 C
static.exe!_cinit(int initFloatingPrecision=1) Line 208 + 0xf bytes C
static.exe!mainCRTStartup() Line 266 + 0x7 bytes C
作為對(duì)比,我在AAA的析構(gòu)函數(shù)出設(shè)置了斷點(diǎn),調(diào)用堆棧如下:
static.exe!AAA::~AAA() Line 19 C++
static.exe!aaaa::`dynamic atexit destructor'() + 0x28 bytes C++
static.exe!doexit(int code=0, int quick=0, int retcaller=0) Line 451 C
static.exe!exit(int code=0) Line 311 + 0xd bytes C
static.exe!mainCRTStartup() Line 289 C
由此我們可以看出程序的實(shí)際入口點(diǎn)位mainCRTStartup而不是main函數(shù)(相對(duì)于ANSI的控制臺(tái)程序而言)。
我們來分析一下_cinit函數(shù):
注釋中有一句[3. General C initializer routines],看來該函數(shù)的功能之一是完成C的初始化例程。
函數(shù)的核心代碼如下:
/*
* do initializations
*/
initret = _initterm_e( __xi_a, __xi_z );
/*
* do C++ initializations
*/
_initterm( __xc_a, __xc_z );
看來該函數(shù)主要進(jìn)行C、C++的初始化。我們進(jìn)一步分析函數(shù)_initterm_e和_initterm,兩個(gè)函數(shù)的功能進(jìn)本相同,都是遍歷函數(shù)指針(由參數(shù)指定函數(shù)指針的開始位置[__xi_a、__xi_z]、結(jié)束位置[__xc_a、__xc_z]),如果函數(shù)指針不為null,那么調(diào)用該函數(shù)。
那么__xi_a、__xi_z和__xc_a、__xc_z到底代表了什么呢?在cinitexe.c文件中有如下代碼:
#pragma data_seg(".CRT$XIA")
_CRTALLOC(".CRT$XIA") _PVFV __xi_a[] = { NULL };
#pragma data_seg(".CRT$XIZ")
_CRTALLOC(".CRT$XIZ") _PVFV __xi_z[] = { NULL };/* C initializers */
#pragma data_seg(".CRT$XCA")
_CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = { NULL };
#pragma data_seg(".CRT$XCZ")
_CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = { NULL };/* C++ initializers */
#pragma comment(linker, "/merge:.CRT=.data")
可以看出這四個(gè)變量分別在數(shù)據(jù)段.CRT$XIA、.CRT$XIZ、.CRT$XCA、.CRT$XCZ中。當(dāng)連接器布局代碼時(shí),它按根據(jù)的名稱,按照字母排序的規(guī)則,排列所有段。這樣在段.CRT$XIA中的變量出現(xiàn)在段.CRT$XIZ所有變量之前,從而形成鏈表。對(duì)于.CRT$XCA、.CRT$XCZ數(shù)據(jù)段同理。最后這四個(gè)數(shù)據(jù)段被合并到.data數(shù)據(jù)段中。
再看看這些變量的類型,typedef void (__cdecl *_PVFV)(void); 所以這些變量組成了2個(gè)初始化函數(shù)指針鏈表。
調(diào)試過程中,看到__xc_a、__xc_z鏈表中,指向的初始化函數(shù)很多是構(gòu)造函數(shù),如:
static std::_Init_locks initlocks;
static filebuf fout(_cpp_stdout);
extern _CRTDATA2 ostream cout(&fout);
cout對(duì)象也在此時(shí)被構(gòu)造。
對(duì)于析構(gòu)函數(shù)的調(diào)用也是采用相同的方式,只是此時(shí)每一種初始化,都有一種終止函數(shù)與之對(duì)應(yīng)。
4、 總結(jié)
l 編譯、連接程序時(shí),編譯器將所有全局對(duì)象的初始化函數(shù)放入.CRT$Xx中,連接器將所有的.CRT$XCx段合并成為.rdata數(shù)據(jù)段。在.CRT$XCA 到 .CRT$XCZ的所有段的數(shù)據(jù)組成初始化函數(shù)指針列表。
l 函數(shù)執(zhí)行時(shí),_initterm( __xc_a, __xc_z )函數(shù)調(diào)用所有的初始化函數(shù)。構(gòu)造全局對(duì)象。構(gòu)造對(duì)象完畢,調(diào)用atexit函數(shù)來保證析構(gòu)函數(shù)的調(diào)用。Modern C++ Design就是通過控制調(diào)用atexit函數(shù)來決定對(duì)象的析構(gòu)順序的。
l 對(duì)于靜態(tài)對(duì)象使用atexit來保證析構(gòu)函數(shù)的調(diào)用。
l 程序結(jié)束時(shí),調(diào)用exit來析構(gòu)全局對(duì)象或靜態(tài)對(duì)象。
本文來自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/coffeemay/archive/2006/11/19/1395250.aspx