Run_Before_or_after_main

一般程序都是会从main函数开始进行,但事实上 main 函数之前也发生了很多操作。在 main 函数开始前,分成两部分 “系统调用部分” 和 “C++ 程序自身的部分”

main() 执行前

  1. 入口函数对运行库和程序运行环镜进行初始化,包括 堆、I/O、线程、全局变量构造等等。
  2. 入口函数完成初始化后,调用 main 函数,正式开始执行程序主体部分。

main函数执行之前,主要就是初始化系统相关资源: 1.设置栈指针 2.初始化static静态和global全局变量,即data段的内容 3.将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容 4.将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数

main运行前可运行哪些代码

1.全局对象的构造函数会在 main 函数之前执行。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class simpleClass{
public:
simpleClass( ){
cout << "simpleClass constructor.." << endl; //step2
}
};

simpleClass g_objectSimple; //step1全局对象
// 这里调用了全局对象的构造函数

int _tmain(int argc, _TCHAR* argv[]){ //step3
return 0;
}

//可单步调试查看执行顺序为step1、step2、step3。

2. 全局变量、对象和静态变量、对象的空间分配和赋初值

发生在执行main函数之前,而main函数执行完后, 还要去执行一些诸如释放空间、释放资源使用权等操作

静态变量构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class simpleClass{
public:
simpleClass( ){
cout << "simpleClass constructor.." << endl; //step2
}
};

class simpleClassTwo{
public:
static simpleClass m_sSimpleClass;
};

simpleClass simpleClassTwo::m_sSimpleClass = simpleClass(); //step1 静态对象

int _tmain(int argc, _TCHAR* argv[]){ //step3
return 0;
}

全局变量的赋值函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

int f(){
printf("before");
return 0;
}

int A = f();

int main(){
return 0;
}

全局lambda变量调用

1
2
3
4
5
6
7
8
9
int a = []() {
std::cout << "a";
return 0;
}();

int main() {
std::cout << "b";
return 0;
}

3.进程启动后,要执行一些初始化代码

(如设置环境变量等),然后跳转到main执行。全局对象的构造也在main之前

在写程序时,比如一个模块,通常要有 initialize 和 de-initialize,但是我们写 C 程序的时候为什么有些模块没有这两个过程么呢?比如我们程序从 main 开始就可以 malloc,free,但是我们在 main 里面却没有初始化堆。再比如在 main 里面可以直接 printf,可是我们并没有打开标准输出文件啊。

操作系统装载程序之后,首先运行的代码并不是main的第一行,而是某些特别的代码,这些代码准备好main函数执行说需要的环境,并且负责调用main函数,这时候你才可以再main函数里放心大胆的写各种代码:申请内存、使用系统调用、触发异常、访问IO。在main函数返回之后,他会记录main函数的返回值,调用atexit注册的函数,然后结束进程。 ——《程序员的自我修养–链接、装载与库》

那main() 执行后呢?

main函数执行完毕后,返回到入口函数,入口函数进行清理工作,包括全局变量的析构、堆销毁、关闭I/O等,然后系统调用结束进程。

main函数结束可以通过 return 0;或者 exit(0) 来结束,此时程序并非直接结束,而是先调用一些终止处理程序然后再结束。可以使用int atexit(void (*func)(void));来追加自定义终止处理程序,终止处理程序由 exit函数自动调用,调用顺序与登记顺序相反。

如果main函数发生了异常或者使用_exit和_Exit来退出程序,则不会调用终止处理程序。

微信的mars库中对运行在main前和main后的函数封装方法

运行前用全局变量和运行后用atexit函数, 使用BOOT_RUN_STARTUP的函数,在main运行前调用 使用BOOT_RUN_EXIT的函数,在main运行后调用

使用全局变量和atexit实现函数在main运行前和运行后运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <QCoreApplication>
#include <QtDebug>

class Tmp {
public:
Tmp() {
qDebug() << Q_FUNC_INFO;
}
~Tmp() {
qDebug() << Q_FUNC_INFO;
}
};

int main_before() {
qDebug() << Q_FUNC_INFO;
return 0;
}

void main_after() {
qDebug() << Q_FUNC_INFO;
}

int doExit(void (*func)(void)) {
return atexit(func);
}

int nBefore = main_before();
int nAfter = doExit(main_after);
Tmp oTmp;

int main(int argc, char *argv[]){
qDebug() << Q_FUNC_INFO;
// QCoreApplication app(argc, argv);
// return app.exec(); // 进入qt的事件循环
return 0;
}
/* 输出
int main_before()
Tmp::Tmp()
int main(int, char**)
Tmp::~Tmp()
void main_after()
*/

4. 通过关键字__attribute__

让一个函数在主函数之前运行,进行一些数据初始化、模块加载验证等。

如果是GNUC的编译器(gcc,clang),就在你要执行的方法前加上 attribute((constructor))

1
2
3
4
5
6
7
8
9
#include<stdio.h>

__attribute__((constructor)) void func(){
printf("hello world\n");
}

int main(){
printf("main\n"); //从运行结果来看,并没有执行main函数
}

同理,如果想要在main函数结束之后运行, 可加上__attribute__((destructor))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>

void func(){
printf("hello world\n");
//exit(0);
return 0;
}

__attribute__((constructor))void before(){
printf("before\n");
func();
}


__attribute__((destructor))void after(){
printf("after\n");

}

int main(){
printf("main\n"); //从运行结果来看,并没有执行main函数
}