[轉]main 之前與之後

【轉自CLive Studio 百度空間】

我之所以提出這個問題,緣於一些IT公司招聘開發人員的筆試題或者面試題:C++ 中能不能讓一些代碼在 main() 之前或者之後執行?

答案理所當然可以的。

這可以有很多實現方法。下面例舉:

1、一般來說,全局域的變量(包括靜態變量)賦值、初始化等工作都是在main之前執行的。此時初始化變量調用的普通賦值函數,初始化對象調用的類的構造函數,都是行之有效的方法。

比較典型的是靜態變量通過靜態函數賦值、靜態對象的初始化,這不僅對於C++,對於其他編程語言,如Java或C#也同樣適用。

main()函數之前:

example 1.1 one of C++ samples

class Dummy {
public:
Dummy() { run_before_main(); }
};

Dummy dummy;

int main() {
...
}

example 1.2: one of Java samples
public class Test {

private static int dummy = foo();

private static int foo() {
   System.out.println("This is executed first.");
   return 0;
}

public static void main(String[] args) {
   System.out.println("This is executed next.");
}
}

同理,利用全局域的變量(包括靜態變量)的析構可以在main() 之後執行代碼。

example 1.3 one of C++ samples

class A
{  
public:  
A() {
}
~A() {
   printf("This is executed next./n");
}
};

A a;

int main(void) {
printf("This is executed first./n");  
}

example 1.3 one integrated of C++ samples
int main(int argc) {
printf("This is executed with %d./n",argc);
return argc;
}

int c = main(521);
class A
{  
public:  
A() {
   printf("This is A's constructor./n");
}
~A() {
   //extern c;
   //printf("%d This is executed next./n",c);
   printf("This is executed next./n");
}
};

 

 

Output:
This is executed with 521.
This is A's constructor.
This is executed with 1.
This is executed next.

如果把int c = main(521);這一行放到最後,輸出則會變爲:

This is A's constructor.
This is executed with 521.
This is executed with 1.
This is executed next.

下面再給一段代碼,大家想一想執行以後會輸出什麼?

example 1.4 in C++

int main(int argc) {
printf("This is executed with %d./n",argc);
return argc;
}

class A
{  
public:  
A() {
   extern c;
   printf("This is A's constructor with %d./n", c);
}
~A() {
   printf("This is executed next./n");
}
};

A a;
int c = main(521);

Output:
This is A's constructor with 0.
This is executed with 521.
This is executed with 1.
This is executed next.

可以看到extern關鍵字對變量的初始化時機沒有任何影響;同時,全局int如果不賦值其值爲0(double同樣)。

java也是可以在main()之前調用main()函數的:

example 1.5 in Java

public class Test {

private static int dummy = foo();

private static int foo() {
   main((new String[1]));
   System.out.println("This is executed first.");
   return 0;
}

public static void main(String[] args) {
   System.out.println("This is executed next. "+args.length);
}
}

這充分說明了同C++一樣,Java的main()也只不過是呈現給程序員的表面的符號而已。

2、調用C的庫函數。

main()之前執行:

example 2.1

在GCC中可以這樣
#include <stdio.h>
#include <string.h>

void first() __attribute__ ((constructor));

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,這樣,整個控制檯生命纔算進入僵死狀態。  
然後等待系統回收。不過這個過程不在代碼中而已。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章