操作系统装载程序之后,首先运行的代码并不是main的第一行,而是某些别的代码,这些代码负责准备好main函数执行所需要的环境,并且负责调用main函数, 运行这些代码的函数称为入口函数或入口点(Entry Point),视平台的不同而有不同的名字。程序的入口点实际上是一个程序的初始化和结束部分,它往往是运行库的一部分。

一个典型的程序运行步骤

a. 操作系统在创建进程后,把控制权交到了程序的入口,这个入口往往是运行库中的某个入口函数。
b. 入口函数对运行库和程序运行环境进行初始化,包括堆、I/O、线程、全局变量构造,等等。
c. 入口函数在完成初始化之后,调用main函数,正式开始执行程序主体部分。
d. main函数执行完毕以后,返回到入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭I/O等,然后进行系统调用结束进程。

main函数入口点

MSVC的CRT默认的入口函数名为 mainCRTStartup

  1. VC里面预定义的一些全局变量,其中_osver和_winver表示操作系统的版本,_winmajor是主版本号等。通过调用GetVersionExA(这是一个Windows API)来获得当前的操作系统版本信息
  2. 一开始进行内存分配的不是malloc而是_alloca,此时还没有初始化堆,因为在程序的一开始堆还没有被初始化,而alloca是唯一可以不使用堆的动态分配机制。alloca可以在栈上分配任意大小的空间(只要栈的大小允许),并且在函数返回的时候会自动释放,就好像局部变量一样。
  3. 由于没有初始化堆,所以很多事情没法做,第一步把堆先初始化了:
    if ( !_heap_init(0) )
    {
        fast_error_exit(_RT_HEAPINIT);
    }

- 这里使用_heap_init函数对堆(heap)进行了初始化,如果堆初始化失败,那
  么程序就直接退出了。
- 它仅仅调用了HeapCreate的API创建了一个系统堆,那么MSVC的malloc其实
  也是调用这个API将堆管理过程交给系统
  1. 使用_ioinit函数初始化了I/O,接下来这段代码调用了一系列函数进行各种初始化,包括:

    1. _setargv:初始化main函数的argv参数。
    2. _setenv:设置环境变量。
    3. _cinit:其他的C库设置。
    4. 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来储存,有些场合使用指针来表示。
    5. 在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的返回值返回。
      

且行且歌_C
62 声望8 粉丝

逝者如斯夫,不舍昼夜