通常,我們為了使自己的程式結束,會在主函數中使用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