C++:Windows下return,exit和ExitProcess的区别和分析

来源:互联网 发布:互联网金融 中国 知乎 编辑:程序博客网 时间:2024/05/16 14:28

转自:http://www.cnblogs.com/jcss2008/archive/2009/03/18/1415519.html

通常,我们为了使自己的程序结束,会在主函数中使用return或调用exit()。在windows下还有ExitProcess()和TerminateProcess()等函数。
本文的目的是比较以上几种结束程序的方式的区别,并分析其原理。

首先我们用一个例子来说明几种结束方式的区别。
测试环境为Windows XP HOME SP2,编译器为Visual Studio.net 2003

测试代码如下:

#include#include#includeclass 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结束进程。

原创粉丝点击