int main() {
printf("This function is %s ", __FUNCTION__);
return 0;
}
void first() {
printf("This %s is before main ", __FUNCTION__);
}
main() 之后执行:CRT会执行另一些代码,进行处理工作。使用atexit()或_onexit()函数,注册一个函数。
example 2.2
#include <stdlib.h>
int atexit(void(*function)(void));
#include <stdlib.h>
#include <stdio.h>
void fn1(void),fn2(void),fn3(void),fn4(void);
int main(void){
atexit(fn1);
atexit(fn2);
atexit(fn3);
atexit(fn4);
printf("This is executed first./n");
}
void fn1(){
printf("next./n");
}
void fn2(){
printf("executed ");
}
void fn3(){
printf("is ");
}
void fn4(){
printf("This ");
}
3、修改定义main入口的文件。main入口其实是由编译器提供的一个库文件定义的,并不是固化在编译器内核的。因此如果需要的话,可以随意更改。当然我们并不建议这样。
在 windows 下看 VC的源代码里有 crt0.c 这个源文件,这个就是定义main入口的文件,如果你愿意可以在里面加任何语句,然后重新编译。在VC里大概是这个样子:
void __cdecl __crt0 (
)
{
int mainret;
char szPgmName[32];
char *pArg;
char *argv[2];
#ifndef _M_MPPC
void *pv;
/* This is the magic stuff that MPW tools do to get info from MPW*/
pv = (void *)*(int *)0x316;
if (pv != NULL && !((int)pv & 1) && *(int *)pv == 'MPGM') {
pv = (void *)*++(int *)pv;
if (pv != NULL && *(short *)pv == 'SH') {
_pMPWBlock = (MPWBLOCK *)pv;
}
}
#endif /* _M_MPPC */
_environ = NULL;
if (_pMPWBlock == NULL) {
__argc = 1;
memcpy(szPgmName, (char *)0x910, sizeof(szPgmName));
pArg = _p2cstr_internal(szPgmName);
argv[0] = pArg;
argv[1] = NULL;
__argv = argv;
#ifndef _M_MPPC
_shellStack = 0; /* force ExitToShell */
#endif /* _M_MPPC */
}
#ifndef _M_MPPC
else {
_shellStack = _GetShellStack(); //return current a6, or first a6
_shellStack += 4; //a6 + 4 is the stack pointer we want
__argc = _pMPWBlock->argc;
__argv = _pMPWBlock->argv;
Inherit(); /* Inherit file handles - env is set up by _envinit if needed */
}
#endif /* _M_MPPC */
/*
* call run time initializer
*/
__cinit();
mainret = main(__argc, __argv, _environ);
exit(mainret);
}
注意:每个编辑器的实现是不一样的。
4、利用多线程。这对于目前大多编程语言都适用。
example 稍候。
总结:
其实main 是在
mainCRTStartup中被调用的
在main之前会调用一系列初始化函数来初始化这个进程
而在main之后会调用exit(int)来进行进程的清理工作
#ifdef WPRFLAG
__winitenv = _wenviron;
mainret = wmain(__argc, __wargv, _wenviron);
#else /* WPRFLAG */
__initenv = _environ;
mainret = main(__argc, __argv, _environ);
#endif /* WPRFLAG */
#endif /* _WINMAIN_ */
exit(mainret);
楼上是说onexit是在main()之前, 来看看代码便知
exit的代码
void __cdecl exit (
int code
)
{
doexit(code, 0, 0); /* full term, kill process */
}
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 */
#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) {
_PVFV * pfend = __onexitend;
while ( --pfend >= __onexitbegin )
/*
* if current table entry is non-NULL,
* call thru it.
*/
if ( *pfend != NULL )
(**pfend)(); // 在这里循环调用onexit
}
/*
* 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 */
if (retcaller) {
#ifdef _MT
_unlockexit(); /* unlock the exit code path */
#endif /* _MT */
return;
}
_C_Exit_Done = TRUE;
ExitProcess(code);
}
区别一下,系统在main前和main后,是为我们做了很多工作的,单单一个空的main,如果你用汇编级调试器去调试之后,发现起点不是main,而main只是其中一个空函数而已。
main的结束不等于整个程序的结束,也不等于C生命期的结束……
ITOM中有很清楚的阐述,关于全局数据区中创建的对象是如何销毁,以及用怎样的顺序销毁的。
如果愿意,可以跟踪一个全局对象的创建和销毁过程,你从那个函数中返回出来的时候,都不是正常的main途径了,做到这点很简单,在你的构造函数和析构函数中加上如下代码就可以了,其实也就是一个设置断点异常的过程,
__asm int 3
然后用trap step,可以跟踪出函数,发现其实进入了crt0.h中(MS的编译器是如此)
然后跟着可以发现main函数。
调用main前和调用main分别有一个初始化全局和销毁全局部分……
跟踪的时候,可以跟到那个地方。
最后的结束,其实最终都需要执行系统API ExitProcess,这样,整个控制台生命才算进入僵死状态。
然后等待系统回收。不过这个过程不在代码中而已。