目录
起因
今天单步调试时按F11按多了(我更习惯用F11而不是F10),main函数结束后程序忽然跳到了exe_common.inl里面!
于是兴致大起,写了一个比Hello World还简单的程序来做实验:
int main()
{
return 0;
}
简单不?这个不是重点,我们现在开始调试
经过
exe_common.inl
按4下F11就跳到了exe_common.inl里
telemetry.cpp
又按4下F11,VS提示未找到telemetry.cpp,于是手动创建了一个空的, 看看能不能迷惑住VS,居然真的迷惑住了
||ヽ(* ̄▽ ̄*)ノミ|Ю
traceloggingprovider.h
又按了大约10次F11,VS又提示需要 traceloggingprovider.h,于是又创建了一个(#^.^#)
又回到telemetry.cpp里了?
又按了5下F11,它跳回了telemetry.cpp里面。又按了两下,他又跳到了exe_common.inl中。
又跳到exe_common.inl里了
又按两下,跳到utility_desktop.cpp里了
utility_desktop.cpp
再按11下,又跳回(exe_common.inl)去了。
exe_common.inl他又来了
再次跳到exe_common.inl里面时,我有一种不祥的预感:下一行就是exit函数了o(╥﹏╥)o
exit函数——程序的退出
再按两下,运行到了函数调用exit(main_result);里面,跳到这里程序才算退出了。
继续探索——main函数的调用者
exe_common.inl
但我的实验还没结束,我再次定位到exe_common.inl里,发现main函数是在那里被调用的:
static int __cdecl invoke_main() throw()
{
return main(__argc, __argv, _get_initial_narrow_environment());
}
然后启动搜索大法(注意是所有的 Visual C++ 目录),发现有4个源文件都导入了这个模块:
- exe_main.cpp
- exe_wmain.cpp
- exe_winmain.cpp
- exe_wwinmain.cpp
exe_main.cpp
要的当然是exe_main.cpp啊!点进去一看,(不算空行和注释)居然只有6行!
#define _SCRT_STARTUP_MAIN
#include "exe_common.inl"
extern "C" int mainCRTStartup()
{
return __scrt_common_main();
}
结果
结论
然后再次启动搜索大法,没搜到那里使用exe_main.cpp,于是得出结论:
main函数不是操作系统调用的,是exe_common.inl调用的!
exe_common.inl也不是操作系统调用的,是exe_main.cpp调用的!
exe_main.cpp才是操作系统调用的!
函数/模块调用次序
最后我整理了一下函数/模块使用次序(括号里的是模块,不在括号里的是函数):
- (exe_main.cpp)
- (exe_common.inl)
- (源.cpp)main
- (exe_common.inl)invock_main
- (exe_common.inl)__telemetry_main_return_trigger
- (telemetry.cpp)
- (traceloggingprovider.h)
- (telemetry.cpp)
- (exe_common.inl)
- (utility_desktop.cpp)__scrt_is_managed_app
- (exe_common.inl)exit
嗨!别走!还有呢!
提示:以下代码有点长,可以配合后面的程序注解看看。
以下是“程序不从main函数开始,不从main函数结束”的另一个证明方法(原谅我英语不太好,如果英文有语法错误,请指出):
#include <cstdio>
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
string str("aa");
class my_class
{
public:
int n;
my_class()
{
printf("default ctor.\n");
}
my_class(int n)
{
printf("one arg:int n ctor\n");
this -> n = n;
}
~my_class()
{
printf("dtor.\n");
}
};
my_class a,b(1);
int func()
{
printf("func() called.\n");
return 0;
}
int aaa=func();
void func2()
{
printf("func2() called.\n");
}
int main(int argc,char *argv[])
{
printf("start of main.\n");
cout << "Before the main function is called,IO initialization was completed." << endl;
printf("a and b is right. b.n = %d\n",b.n);
int *p=new int;
printf("Heap initialization was completed too.\n");
delete p; //防止内存泄露
printf("argc and argv's value is right,too.\nargc = %d,argv[0]=%s,argv[1]=%s,argv[2]=%s\n",argc,argv[0],argv[1],argv[2]);
int n;
scanf("%d",&n);
atexit(&func2);
printf("Input initialization was completed!\nn=%d!\n",n);
printf("str + to_string(n) = %s\n",(str+to_string(n)).c_str());
cout << "end of main.\n";
return 1;
}
其实这个程序的逻辑还是比较清晰的:
1.定义一个string型变量(调用了string型变量的构造函数)
2.定义一个类my_class,有两个构造函数,一个析构函数和一个成员变量n
3.定义my_class类的两个实例,分别叫a和b,对a调用第一个构造函数,对b调用第二个构造函数
4.定义两个函数,一个叫做func,没有参数,返回值为int,另一个叫做func2,没有参数,没有返回值
5.调用这个函数,返回值赋值到变量aaa中
6.进入主程序
7.输出b.n,argc,argv[0],argv[1],argv[2]
8.定义并输入n
9.用atexit函数注册func2函数
10.输出n和str+to_string(n)
编译:
g++ -std=c++11 xxx.cpp
运行(看好!):
a at 1
输入:
121
输出:
default ctor.
one arg:int n ctor
func() called.
start of main.
Before the main function is called,IO initialization was completed.
a and b is right. b.n = 1
Heap initialization was completed too.
argc and argv's value is right,too.
argc = 3,argv[0]=a,argv[1]=at,argv[2]=1
121
Input initialization was completed!
n=121!
str + to_string(n) = aa121
end of main.
func2() called.
dtor.
dtor.
你看,在start of main之前和end of main之后都有输出,而在start of main之前,其实IO初始化已经完成(可以用printf,scanf,cin和cout),a,b初始化完成(b.n=1),argc和argv被正确传入,str被正确构造。而在main之后,调用了str,a和b的析构函数,还调用了由atexit函数注册的func2函数。
所以,铁证如山,程序真的不是从main函数开始执行的!