静态对象、全局对象与程序的运行机制


1、  
在介绍静态对象、全局对象与程序的运行机制之间的关系之前,我们首先看一下atexit函数。
atexit函数的声明为:int atexit( void ( __cdecl *func )( void ) );
参数为函数指针,返回值为整型,0表示成功,其他表示失败。当程序运行结束时,他调用atexit函数注册的所有函数。如果多次调用atexit函数,那么系统将以LIFO(last-in-first-out)的方式调用所有的注册函数。
举例如下(代码摘自MSDN):
#include <stdlib.h>
#include <stdio.h>
 
void fn1( void ), fn2( void ), fn3( void ), fn4( void );
 
void main( void )
{
  atexit( fn1 );
  atexit( fn2 );
  atexit( fn3 );
  atexit( fn4 );
  printf( "This is executed first./n" );
}
 
void fn1()
{
  printf( "next./n" );
}
 
void fn2()
{
  printf( "executed " );
}
 
void fn3()
{
  printf( "is " );
}
 
void fn4()
{
  printf( "This " );
}
编译、运行程序后,程序的输出为:
This is executed first.
This is executed next.
注册函数的顺序为:fn1、fn2、fn3、fn4,但是调用顺序为fn4、fn3、fn2、fn1。
2、理解了atexit函数之后,我们就可以来看看局部静态对象了。
class AAA{ … } ;
AAA* createAAA()
{
        static AAA a ;
        return &a ;
}
在调试状态下,汇编代码如下(请观察蓝色标记出来的代码):
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]为方便说明加入的字符,实际代码中并不存在。
[1]语句很明显的调用AAA的构造函数。
[2]语句将442410h压入栈中。
[3]语句调用atexit函数,根据我们的了解,atexit的参数应该是函数指针。那么我们来分析一下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调用规范的风格。
[2]语句调用AAA的析构函数。

程序结束时,将调用atexit函数注册的
442410h处的函数,进而调用了AAA的析构函数。从而保证了析构函数的调用
 
3、   了解了局部静态对象之后,我们来看看全局对象。
我们知道全局对象必须在main函数前已经被构造。为了弄清楚全局对象何时被构造,我在全局对象的实例化处设置了断点,调用堆栈如下:

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

作为对比,我在AAA的析构函数出设置了断点,调用堆栈如下:

     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

由此我们可以看出程序的实际入口点位mainCRTStartup而不是main函数(相对于ANSI的控制台程序而言)。
我们来分析一下_cinit函数:
注释中有一句[3. General C initializer routines],看来该函数的功能之一是完成C的初始化例程。
函数的核心代码如下:
/*
         * do initializations
         */
        initret = _initterm_e( __xi_a, __xi_z );
/*
         * do C++ initializations
         */
        _initterm( __xc_a, __xc_z );
看来该函数主要进行C、C++的初始化。我们进一步分析函数_initterm_e和_initterm,两个函数的功能进本相同,都是遍历函数指针(由参数指定函数指针的开始位置[__xi_a、__xi_z]、结束位置[__xc_a、__xc_z]),如果函数指针不为null,那么调用该函数。
那么__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")
可以看出这四个变量分别在数据段.CRT$XIA、.CRT$XIZ、.CRT$XCA、.CRT$XCZ中。当连接器布局代码时,它按根据的名称,按照字母排序的规则,排列所有段。这样在段.CRT$XIA中的变量出现在段.CRT$XIZ所有变量之前,从而形成链表。对于.CRT$XCA、.CRT$XCZ数据段同理。最后这四个数据段被合并到.data数据段中。
再看看这些变量的类型,typedef void (__cdecl *_PVFV)(void); 所以这些变量组成了2个初始化函数指针链表。
调试过程中,看到__xc_a、__xc_z链表中,指向的初始化函数很多是构造函数,如:
static std::_Init_locks initlocks;
static filebuf fout(_cpp_stdout);
extern _CRTDATA2 ostream cout(&fout);
cout对象也在此时被构造。
对于析构函数的调用也是采用相同的方式,只是此时每一种初始化,都有一种终止函数与之对应。
4、  总结
l         编译、连接程序时,编译器将所有全局对象的初始化函数放入 .CRT$Xx中,连接器将所有的.CRT$XCx段合并成为.rdata数据段。在.CRT$XCA 到 .CRT$XCZ的所有段的数据组成初始化函数指针列表
l    函数执行时_initterm( __xc_a, __xc_z )函数调用所有的初始化函数。构造全局对象。构造对象完毕,调用atexit函数来保证析构函数的调用。Modern C++ Design就是通过控制调用atexit函数来决定对象的析构顺序的。
l    对于静态对象使用atexit来保证析构函数的调用
l    程序结束时,调用 exit来析构全局对象或静态对象
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值