C++全域變數的生老病死

來源:互聯網
上載者:User

也是最近被問的一個問題,全域變數在哪個階段初始化?

 

這個問題到沒被問倒,全域變數在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 CRTDLLvoid __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)()->編譯器產生資源失敗處理代碼->調用到解構函式存根函數->跳轉到實際的解構函式地址執行資源釋放

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

 

  

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.