赞
踩
一般的对象实例化在什么时候实例化的呢?
是不是在main函数运行到那里的时候,然后创建对象,会调用类里面的构造函数。
那当我们遇到全局/静态对象的时候,它是不是也是需要在main函数里面慢慢构造呢?
答案是 :
不是的。
全局/静态对象 的构造函数调用实在main函数之前的。
有人在疑问?main函数之前还有函数?不是从main函数开始运行的吗?
全局对象所在的内存地址空间为全局数据区,而局部对象的内存地址空间在栈中,他们触发构造函数和析构函数时机不同。
全局/静态对象构造函数实现:
在启动函数mainCRTStartup中有个_cinit,全局对象的构造函数就是在此函数中实现的,
在函数_cinit的_initterm函数调用中,初始化了全局对象。_initterm实现代码片段如下:
while(pfbegin<pfend){//pfbegin==_xc_a pfend==_xc_z
if(*pfbegin!=NULL)
(**pfbgin)()//调用每一个初始化或构造代理函数
++pfbegin;
}
当pfbegin不为NULL时进入if语句块。执行(**pfbegin)();
后并不会进入全局对象的构造函数中,而是进入编译器提供的构造代理函数中,由一个负责全局对象的构造代理函数完成对全局构造函数的调用过程。
构造代理函数代码如下:
由于构造函数需要传递对象的首地址作为this指针,而且构造函数可以带各类参数,因此编译器将为每个全局对象生成一段传递this指针和参数的代码,然后使用无参的代理函数去调用构造函数。
全局/静态对象析构函数实现:
全局/静态对象相同,其构造函数在函数_cinit的第二个_initterm调用中被构造。他们的析构函数的调用时机是在main函数执行完毕之后。既然构造函数出现在初始化过程中,对应的析构函数就会出现在程序结束出。我们来看一下mainCRTStartup函数,它在调用main函数结束后使用了exit用来终止程序,如下:
mainret=main(_argc,_argv,_environ);
//WPRFLAG
//_WINMAIN_
exit(mainret);
在main函数调用结束后,由exit来结束进程,从而终止程序的运行。全局对象的析构函数的调用也在其中,由exit函数内的doexit实现,关键代码如下:
if(_onexitbegin)//_onexitbegin为函数指针数组的首地址
{
_PVFV * pfend=_onexitend; //__onexitbegin为函数指针数组的尾地址
while(--pend >=__onexitbegin)//从后向前依次释放全局对象
if(*pend!=NULL)
(**pend)();//调用数组中保存的函数
}
__onexitbegin指向一个指向数组,该数组中保存着各类资源释放时的函数的首地址。编译器实在何时生成这样一个数组的呢?
全局构造函数的调用是在_cinit函数的第二个_initterm函数内完成,而在第二个_initterm函数的初始化函数指针数组。在执行每个全局对象构造代理函数时都会执行对象的构造函数,然后使用atexit注册析构代理函数。
举例:
如果定义一个全局对象CMyString G_MyStringTwo;,该对象的全局析构函数代理函数的分析如下所示:
;该代理函数由编译器添加,无源码对照
;函数入口对照
mov ecx ,offset g_MyStringTwo(0042af7c)
call @ILT+35(MyStringTwo::~MyStringTwo)(00401028)
;函数退出部分
ret
由于函数数组中保存的析构代理函数被定义为无参函数,因此在调用析构函数时无法传递this指针。于是编译器需要为每个全局变量和静态对象建立一个中间代理的析构函数,用于传入全局对象的this指针。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。