main 之前与之后

原创 2012年03月27日 21:32:49

我之所以提出这个问题,缘于一些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,这样,整个控制台生命才算进入僵死状态。  
然后等待系统回收。不过这个过程不在代码中而已。

main 之前与之后

我之所以提出这个问题,缘于一些IT公司招聘开发人员的笔试题或者面试题:C++ 中能不能让一些代码在 main() 之前或者之后执行? 答案理所当然可以的。 这可以有很多实现方法。下面例举: 1、...
  • fatshaw
  • fatshaw
  • 2011年08月23日 15:07
  • 1209

main执行之前与之后

main函数执行之前,主要就是初始化系统相关资源: 1.设置栈指针 2.初始化static静态和global全局变量,即data段的内容 3.将未初始化部分的赋初值:数值型short,int,l...
  • sunmenggmail
  • sunmenggmail
  • 2012年06月01日 14:54
  • 398

[转]main 之前与之后

【转自CLive Studio 百度空间】我之所以提出这个问题,缘于一些IT公司招聘开发人员的笔试题或者面试题:C++ 中能不能让一些代码在 main() 之前或者之后执行?答案理所当然可以的。这可以...
  • dobest9014
  • dobest9014
  • 2010年08月15日 23:16
  • 580

main 函数之前与之后

main 函数执行以前,还会执行什么代码   全局对象的构造函数会在main 函数之前执行。 main 主函数执行完毕后,是否可能会再执行一段代码   可以,可以用_on...
  • gaokewoo
  • gaokewoo
  • 2014年04月26日 21:55
  • 204

iOS App程序在调用main()之前做了那些事情?

1、APP启动时间 1)main之前的系统dylib(动态链接库)和自身App可执行文件的加载的时间 2)main之后执行didFinishLaunchingWithOptions:结束前的时间 ...
  • yu_4074
  • yu_4074
  • 2017年02月10日 11:44
  • 655

在main函数之前调用函数,以及对设计的作用

前几天为新员工写一个简单的测试框架,可让他们方便的写测试用例并且执行。期间遇到一个问题就是如何让他们增加测试用例而用不影响测试框架的代码?c++的单件模式可以解决这个问题,但是其中一个难点是要在mai...
  • chgaowei
  • chgaowei
  • 2011年08月16日 20:02
  • 6544

那些年犯过的错:在main方法之前,到底执行了什么?

本人在做接口测试的时候,需要用一个公共类来把所有的执行的代码,然后这个公共类有hsot和hosttype等属性来区分各个测试环境,然后在去不同的地方取用例和请求接口。在给这些属性复制的时候,我是通过不...
  • Fhaohaizi
  • Fhaohaizi
  • 2017年12月13日 10:39
  • 181

[C/C++] main函数执行前后还会发生什么

问题描述在C/C++语言执行过程中是不是所有的动作都由main()函数引起,来看下面的代码:#include class A{public: A() { printf("Th...
  • honyniu
  • honyniu
  • 2016年05月05日 20:09
  • 1394

IOS程序启动时main函数前执行的部分-load方法和静态函数

1 load部分 1.1 调用堆栈 frame #0: MyApp`+[XXX load] frame #1: libobjc.A.dylib`call_load_methods + ...
  • demondev
  • demondev
  • 2016年08月28日 19:03
  • 1614

main 之前之后的那些事儿

在main函数执行之前 ,总要执行一段代码,如static对象 static函数 以及全局对象的初始化。在退出之前也总要执行一段代码来释放相关的资源。这段代码通常是不可见的 故 一般形式为: in...
  • wangjian8855
  • wangjian8855
  • 2013年09月10日 14:57
  • 558
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:main 之前与之后
举报原因:
原因补充:

(最多只允许输入30个字)