main函式的入口函式

不捨晝夜發表於2019-05-12

作業系統裝載程式之後,首先執行的程式碼並不是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的返回值返回。
      

相關文章