操作系统装载程序之后,首先运行的代码并不是main的第一行,而是某些别的代码,这些代码负责准备好main函数执行所需要的环境,并且负责调用main函数, 运行这些代码的函数称为入口函数或入口点(Entry Point),视平台的不同而有不同的名字。程序的入口点实际上是一个程序的初始化和结束部分,它往往是运行库的一部分。
一个典型的程序运行步骤
a. 操作系统在创建进程后,把控制权交到了程序的入口,这个入口往往是运行库中的某个入口函数。
b. 入口函数对运行库和程序运行环境进行初始化,包括堆、I/O、线程、全局变量构造,等等。
c. 入口函数在完成初始化之后,调用main函数,正式开始执行程序主体部分。
d. main函数执行完毕以后,返回到入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭I/O等,然后进行系统调用结束进程。
main函数入口点
MSVC的CRT默认的入口函数名为 mainCRTStartup
- VC里面预定义的一些全局变量,其中_osver和_winver表示操作系统的版本,_winmajor是主版本号等。通过调用GetVersionExA(这是一个Windows API)来获得当前的操作系统版本信息
- 一开始进行内存分配的不是malloc而是_alloca,此时还没有初始化堆,因为在程序的一开始堆还没有被初始化,而alloca是唯一可以不使用堆的动态分配机制。alloca可以在栈上分配任意大小的空间(只要栈的大小允许),并且在函数返回的时候会自动释放,就好像局部变量一样。
- 由于没有初始化堆,所以很多事情没法做,第一步把堆先初始化了:
if ( !_heap_init(0) )
{
fast_error_exit(_RT_HEAPINIT);
}
- 这里使用_heap_init函数对堆(heap)进行了初始化,如果堆初始化失败,那
么程序就直接退出了。
- 它仅仅调用了HeapCreate的API创建了一个系统堆,那么MSVC的malloc其实
也是调用这个API将堆管理过程交给系统
-
使用_ioinit函数初始化了I/O,接下来这段代码调用了一系列函数进行各种初始化,包括:
- _setargv:初始化main函数的argv参数。
- _setenv:设置环境变量。
- _cinit:其他的C库设置。
-
FILE结构体
struct _iobuf { char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; }; typedef struct _iobuf FILE; - 这个FILE结构中最重要的一个字段是_file,_file是一个整数,通过_file可以访问到内部文件句柄表中的某一项。在Windows中,用户态使用句柄(Handle)来访问内核文件对象,句柄本身是一个32位的数据类型,在有些场合使用int来储存,有些场合使用指针来表示。
-
在MSVC的CRT中,已经打开的文件句柄的信息使用数据结构ioinfo来表示:
typedef struct { intptr_t osfhnd; char osfile; char pipech; } ioinfo; □ 在这个结构中,osfhnd字段即为打开文件的句柄,这里使用8字节整数类型intptr_t来存储。另外osfile的意义为文件的打开属性。而pipech字段则为用于管道的单字符缓冲,这里可以先忽略。 □ 在crt/src/ioinit/c中有一个数组-------ioinfo *_pioinfo[64]; //等效于 ioinfo _pioinfo[64][32] □ 二维上可容纳32个ioinfo结构,因此该表可容纳64 * 32 = 2048 个句柄 □ 而FILE中_file的值就是和上面的osfhnd直接关联 ® _file中第五位到第十位是一维坐标 ® _file中第零位到第四位是二维坐标 □ 应用程序可以通过API GetStartupInfo来继承打开的文件
e. 总体步骤
1. 初始化和OS版本有关的全局变量。 2. 初始化堆。 3. 初始化I/O。 4. 获取命令行参数和环境变量。 5. 初始化C库的一些数据。 6. 调用main并记录返回值。 7. 检查错误并将main的返回值返回。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。