您的位置:首页 > 编程语言 > PHP开发

Windows下return,exit和ExitProcess的区别和分析- -

2009-03-18 15:28 387 查看

通常,我们为了使自己的程序结束,会在主函数中使用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,因此能析构全部对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: