Windows下return,exit和ExitProcess的區別和分析

來源:互聯網
上載者:User

通常,我們為了使自己的程式結束,會在主函數中使用return或調用exit()。在windows下還有ExitProcess()和TerminateProcess()等函數。
本文的目的是比較以上幾種結束程式的方式的區別,並分析其原理。

 

首先我們用一個例子來說明幾種結束方式的區別。
測試環境為Windows XP HOME SP2,編譯器為Visual Studio.net 2003

測試代碼如下:
#include
#include
#include

class Test
{
public:
 Test (int i) {m_i=i; printf ('construct %dn', m_i);};
 ~Test () {printf ('destruct %dn', m_i);};
private:
 int m_i;
};

Test t_1 (1);

int main(int argc, char* argv[])
{
 Test t_2 (2);
 printf('Hello World!n');
// return 0;
// exit (0);
// ExitProcess (0);
}

我們的目標是察看兩種結束方式有什麼不同。

程式在啟動並執行結果為:

使用return 0結束時:
construct 1
construct 2
Hello World!
destruct 2
destruct 1

使用exit (0)結束時:
construct 1
construct 2
Hello World!
destruct 1

使用ExitProcess (0)結束時:
construct 1
construct 2
Hello World!

從結果上我們可以看出來,採用return來結束進程可以正確的析構全域和局部對象。而採用exit()來結束進程時全域對象可以正確析構,但局部對象沒有正確析構。採用ExitProcess(0)結束時全域和局部對象都沒有正確析構。

為什麼會出現這樣的情況呢?
《Windows核心編程》中我們可以得到以下解釋:
'當主線程的進入點函數(WinMain、wWinMain、main或wmain)返回時,它將返回給C/C++運行期啟動代碼,它能夠正確地清楚該進程使用的所有C運行期資源。當C運行期資源被釋放之後,C運行期啟動代碼就顯式的調用ExitProcess,並將進入點函數返回的值傳遞給它。'

那麼,通過跟蹤代碼我們可以發現:
return 0實際上執行了以下操作:
 return 0;
00401035  mov         dword ptr [ebp-0D4h],0
0040103F  lea         ecx,[t_2]
00401042  call        Test::~Test (4010F0h)
00401047  mov         eax,dword ptr [ebp-0D4h]
}
0040104D  push        edx 
0040104E  mov         ecx,ebp
00401050  push        eax 
00401051  lea         edx,ds:[401072h]
00401057  call        _RTC_CheckStackVars (4011E0h)
0040105C  pop         eax 
0040105D  pop         edx 
0040105E  pop         edi 
0040105F  pop         esi 
00401060  pop         ebx 
00401061  add         esp,0D8h
00401067  cmp         ebp,esp
00401069  call        _RTC_CheckEsp (4011B0h)
0040106E  mov         esp,ebp
00401070  pop         ebp 
00401071  ret             
在ret之後,程式返回到啟動main函數的代碼,並執行以下操作:
            if ( !managedapp )
                exit(mainret);
            _cexit();

可見return 0上調用了局部對象t_2的解構函式。


void __cdecl exit (
        int code
        )
{
        doexit(code, 0, 0); /* full term, kill process */
}
void __cdecl _cexit (
        void
        )
{
        doexit(0, 0, 1);    /* full term, return to caller */
}
         
實際上程式調用了doexit函數。
static void __cdecl doexit (
        int code,
        int quick,
        int retcaller
        )
{
#ifdef _DEBUG
        static int fExit = 0;
#endif  /* _DEBUG */

#ifdef _MT
        _lockexit();        /* assure only 1 thread in exit path */
        __TRY
#endif  /* _MT */

            if (_C_Exit_Done == TRUE)                               /* if doexit() is being called recursively */
                    TerminateProcess(GetCurrentProcess(),code);     /* terminate with extreme prejudice */
            _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.
                 */

                if (__onexitbegin) {
                    while ( --__onexitend >= __onexitbegin )
                    /*
                     * if current table entry is non-NULL,
                     * call thru it.
                     */
                    if ( *__onexitend != NULL )
                        (**__onexitend)();
                }

                /*
                 * do pre-terminators
                 */
                _initterm(__xp_a, __xp_z);
            }

            /*
             * do terminators
             */
            _initterm(__xt_a, __xt_z);

#ifndef CRTDLL
#ifdef _DEBUG
            /* Dump all memory leaks */
            if (!fExit && _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) & _CRTDBG_LEAK_CHECK_DF)
            {
                fExit = 1;
                _CrtDumpMemoryLeaks();
            }
#endif  /* _DEBUG */
#endif  /* CRTDLL */

            /* return to OS or to caller */

#ifdef _MT
        __FINALLY
            if (retcaller)
                _unlockexit();      /* unlock the exit code path */
        __END_TRY_FINALLY
#endif  /* _MT */
        if (retcaller)
            return;

        _C_Exit_Done = TRUE;

        __crtExitProcess(code);
}

其中部分原始碼如下:
                if (__onexitbegin) {
00406056  cmp         dword ptr [___onexitbegin (412DA8h)],0
0040605D  je          doexit+70h (406090h)
                    while ( --__onexitend >= __onexitbegin )
0040605F  mov         edx,dword ptr [___onexitend (412DA4h)]
00406065  sub         edx,4
00406068  mov         dword ptr [___onexitend (412DA4h)],edx
0040606E  mov         eax,dword ptr [___onexitend (412DA4h)]
00406073  cmp         eax,dword ptr [___onexitbegin (412DA8h)]
00406079  jb          doexit+70h (406090h)
                    /*
                     * if current table entry is non-NULL,
                     * call thru it.
                     */
                    if ( *__onexitend != NULL )
0040607B  mov         ecx,dword ptr [___onexitend (412DA4h)]
00406081  cmp         dword ptr [ecx],0
00406084  je          doexit+6Eh (40608Eh)
                        (**__onexitend)();
00406086  mov         edx,dword ptr [___onexitend (412DA4h)]
0040608C  call        dword ptr [edx]
                }
0040608E  jmp         doexit+3Fh (40605Fh)

程式在0040608C處跳轉到如下代碼:
0040EC10  push        ebp 
0040EC11  mov         ebp,esp
0040EC13  sub         esp,0C0h
0040EC19  push        ebx 
0040EC1A  push        esi 
0040EC1B  push        edi 
0040EC1C  lea         edi,[ebp-0C0h]
0040EC22  mov         ecx,30h
0040EC27  mov         eax,0CCCCCCCCh
0040EC2C  rep stos    dword ptr [edi]
0040EC2E  mov         ecx,offset t_1 (412760h)
0040EC33  call        Test::~Test (4010F0h)
0040EC38  pop         edi 
0040EC39  pop         esi 
0040EC3A  pop         ebx 
0040EC3B  add         esp,0C0h
0040EC41  cmp         ebp,esp
0040EC43  call        _RTC_CheckEsp (4011B0h)
0040EC48  mov         esp,ebp
0040EC4A  pop         ebp 
0040EC4B  ret             
在這裡,全域變數t_1被析構。
在doexit的最後,程式調用
        __crtExitProcess(code);

void __cdecl __crtExitProcess (
        int status
        )
{
        HMODULE hmod;
        PFN_EXIT_PROCESS pfn;

        hmod = GetModuleHandle('mscoree.dll');
        if (hmod != NULL) {
            pfn = (PFN_EXIT_PROCESS)GetProcAddress(hmod, 'CorExitProcess');
            if (pfn != NULL) {
                pfn(status);
            }
        }

        /*
         * Either mscoree.dll isn't loaded,
         * or CorExitProcess isn't exported from mscoree.dll,
         * or CorExitProcess returned (should never happen).
         * Just call ExitProcess.
         */

        ExitProcess(status);
}
在這裡,終於調用到了ExitProcess。至此,全域對象t_1和局部對象t_2都完成了析構操作。

從分析過程,我們可以得出以下結論。
在Windows下,return 0 的實際執行過程是:

  • 先析構main函數內的局部對象。
  • 返回至調用main的函數。
  • 調用exit函數,由exit函數調用doexit函數,在doexit函數中完成對全域對象的析構。
  • 最後調用ExitProcess結束進程。

所以,ExitProcess不負責任何對象的析構,exit只負責析構全域對象,return 0可以析構局部對象並調用exit,因此能析構全部對象。

ZL=http://chriszz.bokee.com/896602.html  作者:chriszz

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.