程序从main开始的吗?
在执行main之前全局变量已经初始化,main函数的两个参数也被正确传了进来,堆和栈的初始化也已经完成,一些系统I/O也被初始化。
完成上面这些工作的函数称为入口函数(Entry Point)。一个典型的运行步骤大致如下:
·操作系统在创建进程后,把控制权交到了程序的入口,这个入口往往是运行库中的某个函数。
·入口函数对运行库和程序运行环境进行初始化,包括堆、I/O、线程、全局变量构造等。
·入口函数在完成初始化之后,调用main函数,正是开始执行程序主体部分
·main函数执行完毕以后,返回到入口函数,入口函数进行清理工作,包括全局变量的析构,堆销毁、关闭I/O等。然后进行系统调用结束进程。
Glibc的入口函数:
_start
在调用_start前,装载器把用户参数和环境变量压入栈中,按照其压栈的方法,实际上栈顶的元素是argc,而接着其下就是argc和环境变量数组。
_start大概的功能可以用下面的代码描述:
void _start()
{
%ebp = 0;
int argc = pop from stack
char ** argv = top of stack;
__libc_start_main(main, argc, argv, __libc_csu_init, __linc_csu_fini,
edx, top of stack);
}
其中argv除了指向参数表之外,还隐含紧接着环境变量表。这个环境变量表在__libc_start_main里从argv内部提取出来。
MSVC的入口函数:
int mainCRTStartup(void)
{
...
}
在该函数中使用了alloca进行内存分配,这是因为堆还没有初始化,而alloca是唯一可以不使用堆的动态分配机制的函数。
alloca可以再栈上分配任曦大小的空间(只要栈允许),并且在函数放回的时候自动释放,好像局部变量一样。
mainCRTStartup 的总体流程就是:
1.初始化和OS版本有关的全局变量
2.初始化堆
3.初始化I/O
4.获取命令行参数和环境变量
5.初始化C库的一些数据
6.调用main并记录返回值
7.检查错误并将main的返回值返回
MSVC CRT 的入口函数初始化
MSVC的入口函数初始化主要包含两部分,堆初始化和I/O初始化。MSVC的对初始化由函数_heap_init完成(调用HeapCreate)。
I/O初始化工作比较复杂,主要进行如下几个工作:
·建立打开的文件表
·如果能够继承自父进程,那么从父进程获取继承的句柄
·初始化标准输入输出
C语言运行库(C Runtime Library):
C运行库大致包含如下功能:
·启动与退出:包括入口函数及入口函数所依赖的其他函数等
·标准函数:由C语言标准规定的C语言标准库所拥有的函数实现
·I/O:I/O功能的封装和实现
·堆:堆的封装和实现
·语言实现:语言中的一些特殊功能的实现
·调试:实现调试功能的代码
参考:《程序员的自我修养》