C++全局变量的生老病死

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

 

这个问题到没被问倒,全局变量在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);;//Decode之后指向编译器生成的资源释放处理代码

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

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

 

  

posted @ 2012-05-03 23:11  Yarkin  阅读(9606)  评论(0编辑  收藏  举报