C/C++启动函数和C++全局变量的生老病死

转自:http://www.cnblogs.com/SkyMouse/archive/2012/05/03/2481955.html

启动函数的用途如下:1,获取指向新进程的完整命令行的一个指针;2,获取指向新进程的环境变量的一个指针;3,初始化C/C++运行库的全局变量4,初始化所有全局和静态C++类对象的构造函数。对于一个程序而言,在执行main函数之前会执行crtexe.c文件中mainCRTStartup或wmainCRTStartup函数

系统调用WinMain函数:

#ifdef WPRFLAG
            mainret = wWinMain(
#else  /* WPRFLAG */
            mainret = WinMain(
#endif  /* WPRFLAG */
                       (HINSTANCE)&__ImageBase,
                       NULL,
                       lpszCommandLine,
                       StartupInfo.dwFlags & STARTF_USESHOWWINDOW
                        ? StartupInfo.wShowWindow
                        : SW_SHOWDEFAULT
                      );


也是最近被问的一个问题,全局变量在哪个阶段初始化?

 

这个问题到没被问倒,全局变量在mainCRTStartup之后main调用之前,在该阶段应用会完成堆内存的申请(记得哪里还看到如果改了EntryPoint需要自己进行堆内存的申请和管理).

而全局变量也正是在该阶段完成的初始化.

 

然后又被问,那么全局变量在哪里被释放?回答是在应用退出之后main函数退出之后,这个回答也没问题.基本上算是正确的.

但是回头自己仔细想想,那么全局变量又是怎么样被初始化的呢?还真的有点不太清楚,所以出于好奇,今晚开始细细研究研究!

首先写了一段代码如下:

//头文件
class  ClassSizeRes
{
public :
     ClassSizeRes( void );
     ~ClassSizeRes( void );
};
 
//cpp文件
ClassSizeRes::ClassSizeRes( void )
{
}
 
 
ClassSizeRes::~ClassSizeRes( void )
{
}
//main函数处理
ClassSizeRes staticObj;
int  _tmain( int  argc, _TCHAR* argv[])
{
   //...
}

代码大致如此,然后在构造函数处下断点来调试,发现中断之后的调用堆栈如下:

很显然全局变量的初始化确实是在mainCRTStartup之后main调用之前,与之前所理解的确实没有差别,但是编译器又是如何处理的呢?

根据调用堆栈我们可以发现在函数_initterm的定义如下:

#ifdef CRTDLL
void  __cdecl _initterm (
#else  /* CRTDLL */
static  void  __cdecl _initterm (
#endif  /* CRTDLL */
         _PVFV * pfbegin,
         _PVFV * pfend
         )
{
         /*
          * walk the table of function pointers from the bottom up, until
          * the end is encountered.  Do not skip the first entry.  The initial
          * value of pfbegin points to the first valid entry.  Do not try to
          * execute what pfend points to.  Only entries before pfend are valid.
          */
         while  ( pfbegin < pfend )
         {
             /*
              * if current table entry is non-NULL, call thru it.
              */
             if  ( *pfbegin != NULL )
                 (**pfbegin)(); //这里是关键,该函数就是遍历调用无参的函数指针数组
             ++pfbegin;
         }
}

接下来这里的函数指向的地址内容是关键:

这里Pfbegin=0x00f5b30c

查看0x00f5b30c对应内存的内容

0x00F5B30C  00f57f60 00f57fc0 00f58020 (后面内容为00000000)

很显然这里是一个包含3个元素的数组,那么关键就在于这3个元素指向的是什么内容

00f57f60 地址的反汇编内容如下:

ClassSizeRes staticObj;

@0

00F57F60  push        ebp 

00F57F61  mov         ebp,esp 

00F57F63  sub         esp,0C0h 

00F57F69  push        ebx 

00F57F6A  push        esi 

00F57F6B  push        edi 

00F57F6C  lea         edi,[ebp-0C0h] 

00F57F72  mov         ecx,30h 

00F57F77  mov         eax,0CCCCCCCCh 

00F57F7C  rep stos    dword ptr es:[edi] 

00F57F7E  mov         ecx,offset staticObj (0F5E1E4h) 

00F57F83  call        ClassSizeRes::ClassSizeRes (0F51190h)  //调用构造函数@1

00F57F88  push        offset `dynamic atexit destructor for 'staticObj'' (0F590A0h) 

00F57F8D  call        @ILT+190(_atexit) (0F510C3h) 

00F57F92  add         esp,4 

00F57F95  pop         edi 

00F57F96  pop         esi 

00F57F97  pop         ebx 

00F57F98  add         esp,0C0h 

00F57F9E  cmp         ebp,esp 

00F57FA0  call        @ILT+625(__RTC_CheckEsp) (0F51276h) 

00F57FA5  mov         esp,ebp 

00F57FA7  pop         ebp 

00F57FA8  ret 

 

ClassSizeRes::ClassSizeRes:

@2

00F51190  jmp         ClassSizeRes::ClassSizeRes (0F516E0h) @3

 

ClassSizeRes的构造函数

ClassSizeRes::ClassSizeRes(void)

{

@3

00F516E0  push        ebp 

00F516E1  mov         ebp,esp 

00F516E3  sub         esp,0CCh 

00F516E9  push        ebx 

00F516EA  push        esi 

00F516EB  push        edi 

00F516EC  push        ecx 

00F516ED  lea         edi,[ebp-0CCh] 

00F516F3  mov         ecx,33h 

00F516F8  mov         eax,0CCCCCCCCh 

00F516FD  rep stos    dword ptr es:[edi] 

00F516FF  pop         ecx 

00F51700  mov         dword ptr [ebp-8],ecx 

}

00F51703  mov         eax,dword ptr [this] 

00F51706  pop         edi 

00F51707  pop         esi 

00F51708  pop         ebx 

00F51709  mov         esp,ebp 

00F5170B  pop         ebp 

00F5170C  ret 

 

如此看来全局变量的初始化过程如下

Step1:编译器编译之后会根据全局变量声明来生成一些无参函数如上的@0

Step2:程序运行之后,__tmainCRTStartup会调用_initterm函数来调用编译器生成的无参函数

(**pfbegin)();//函数指针,指向编译器自动生成无参函数地址@

这里的pfbegin-pfend都是指向的编译器生成的全局变量初始化函数pfbegin

Step3:无参全局变量初始化函数pfbegin会调用各个类的构造函数完成对象初始化@1

Step3:@1会调用各类的构造函数存根地址(IAT存根地址)

Step4:@2 跳转到构造函数实际实现地址完成对象的初始化

如此到了这一步基本上已经完成了一个全局变量的初始化.

那么相应的释放又是如何实现呢?在析构函数中下断点!发现调用堆栈如下:

很显然实在doexit中调用了相应的析构函数来完成全局变量的析构

static  void  __cdecl doexit (
         int  code,
         int  quick,
         int  retcaller
         )
{
#ifdef _DEBUG
         static  int  fExit = 0;
#endif  /* _DEBUG */
 
#ifdef CRTDLL
         if  (!retcaller && check_managed_app())
         {
             /*
                Only if the EXE is managed then we call CorExitProcess.
                Native cleanup is done in .cctor of the EXE
                If the Exe is Native then native clean up should be done
                before calling (Cor)ExitProcess.
             */
             __crtCorExitProcess(code);
         }
#endif  /* CRTDLL */
 
         _lockexit();        /* assure only 1 thread in exit path */
         __TRY
 
         if  (_C_Exit_Done != TRUE) {
             _C_Termination_Done = TRUE;
 
             /* save callable exit flag (for use by terminators) */
             _exitflag = ( char ) retcaller;  /* 0 = term, !0 = callable exit */
 
             if  (!quick) {
 
                 /*
                  * do _onexit/atexit() terminators
                  * (if there are any)
                  *
                  * These terminators MUST be executed in reverse order (LIFO)!
                  *
                  * NOTE:
                  *  This code assumes that __onexitbegin points
                  *  to the first valid onexit() entry and that
                  *  __onexitend points past the last valid entry.
                  *  If __onexitbegin == __onexitend, the table
                  *  is empty and there are no routines to call.
                  */
 
                 _PVFV * onexitbegin = (_PVFV *) DecodePointer(__onexitbegin);
                 if  (onexitbegin) {
                     _PVFV * onexitend = (_PVFV *) DecodePointer(__onexitend);
                     _PVFV function_to_call = NULL;
 
                     /* save the start and end for later comparison */
                     _PVFV * onexitbegin_saved = onexitbegin;
                     _PVFV * onexitend_saved = onexitend;
 
                     while  (1)
                     {
                         _PVFV * onexitbegin_new = NULL;
                         _PVFV * onexitend_new = NULL;
 
                         /* find the last valid function pointer to call. */
                         while  (--onexitend >= onexitbegin && *onexitend == _encoded_null())
                         {
                             /* keep going backwards. */
                         }
 
                         if  (onexitend < onexitbegin)
                         {
                             /* there are no more valid entries in the list, we are done. */
                             break ;
                         }
 
                         /* cache the function to call. */
                         function_to_call = (_PVFV) DecodePointer(*onexitend);<span style= "background-color: #888888;" ><span style= "color: #ff0000;" ><span style= "background-color: #c0c0c0;" ><em><strong><span style= "background-color: #ffffff;" ><span style= "color: #000000;" >; //Decode之后指向编译器生成的资源释放处理代码
</span></span></strong></em></span></span></span>
                         /* mark the function pointer as visited. */
                         *onexitend = (_PVFV)_encoded_null();
 
                         /* call the function, which can eventually change __onexitbegin and __onexitend */
                         (*function_to_call)(); //又是一个无参函数值得关注,调用编译器生成资源释放代码,然后调用析构函数完成析构,与构造类似
 
                         onexitbegin_new = (_PVFV *) DecodePointer(__onexitbegin);
                         onexitend_new = (_PVFV *) DecodePointer(__onexitend);
 
                         if  ( ( onexitbegin_saved != onexitbegin_new ) || ( onexitend_saved != onexitend_new ) )
                         {
                             /* reset only if either start or end has changed */
                             onexitbegin = onexitbegin_saved = onexitbegin_new;
                             onexitend = onexitend_saved = onexitend_new;
                         }
                     }
                 }
#ifndef CRTDLL
                 /*
                  * do pre-terminators
                  */
                 _initterm(__xp_a, __xp_z);
#endif  /* CRTDLL */
             }
 
#ifndef CRTDLL
             /*
              * do terminators
              */
             _initterm(__xt_a, __xt_z);
#endif  /* CRTDLL */
 
#ifdef _DEBUG
             /* Dump all memory leaks */
             if  (!fExit && _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) & _CRTDBG_LEAK_CHECK_DF)
             {
                 fExit = 1;
#ifndef CRTDLL
                 __freeCrtMemory();
                 _CrtDumpMemoryLeaks();
#endif  /* CRTDLL */
             }
#endif  /* _DEBUG */
 
         }
         /* return to OS or to caller */
 
         __FINALLY
             if  (retcaller)
                 _unlockexit();      /* unlock the exit code path */
         __END_TRY_FINALLY
 
         if  (retcaller)
             return ;
 
 
         _C_Exit_Done = TRUE;
 
         _unlockexit();      /* unlock the exit code path */
 
         __crtExitProcess(code);
}

详细的内容就不多做重复,与构造类似,从

(*function_to_call)()->编译器生成资源失败处理代码->调用到析构函数存根函数->跳转到实际的析构函数地址执行资源释放

如此基本上已经完成了全局变量资源的申请释放.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值