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