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)()->編譯器生成資源失敗處理代碼->調用到析構函數存根函數->跳轉到實際的析構函數地址執行資源釋放

如此基本上已經完成了全局變量資源的申請釋放.

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM