Windows下return,exit和ExitProcess的区别和分析- -
通常,我们为了使自己的程序结束,会在主函数中使用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 的实际执行过程是:
所以,ExitProcess不负责任何对象的析构,exit只负责析构全局对象,return 0可以析构局部对象并调用exit,因此能析构全部对象。
ZL=http://chriszz.bokee.com/896602.html 作者:chriszz |