這篇頂多只能算是讀書心得而已,且不談及「底線開頭」之 library。由於這些動作部份必須上述到 linker ,故分 glibc(gcc) 與 visual c++ (msvc) 探討。要建立完整觀念請洽 「程式設計師的自我修養」第十一章 - 執行階段程式庫。
由於筆者所閱之書籍 (也可能因為平台不同而有所不同名詞),對於 main 只有以「入口函式」、「入口點」、「entry point 」形容 (雖然它不是真正的入口點),沒提到「entry function」此名詞。為說明清晰,本文將以 entry function 作為名詞。
destructor 實作面這篇文章避開。
結論是 : main / WinMain 不是第一個 entry function。
動作原理概述
1. OS 建立該應用程式 (.exe) 之行程後,將控制權轉到該程式之入口,此入口往往為執行階段程式庫之某入口函式。
2. entry function 將對環境進行初始化,包含了 heap、I/O、thread、global variable construct ... etc。
3. main 執行完後,返回到 entry function,entry function 進行清楚工作,包含 global variable destruct、destroy heap、close I/O。
4. 進行系統呼叫結束行程。
主要在 2. 3 點著為筆墨。
glibc (gcc)
1. 動作原理
(1) linker ( ld.exe ) 預設之 entry function 為 _start ,此 entry function 由組語實作,且與平台相關。
(2) 在正式呼叫 _start 前 (正確的說是在 _start function 之前半段),loader 將使用者之參數、環境變數推到 stack。stack 「由上而下」為, argc、 argv、 env (環境變數)。
(3) _start 最後將呼叫 __lib_start_main function,再由 __lib_start_main 間接呼叫 main。pseudo code 如下。
void _start() { %ebp = 0; int argc = pop from stack char** argv = top of stack; __lib_start_main(main, argc, argv, ....); }
(4) 當 main 結束後回到 __lib_start_main,而 __lib_start_main 裡將再呼叫 exit 函式。
_start -> __lib_start_main -> main -> (return to __lib_start_main) -> exit ,看一下 exit 大概長怎樣
void exit(int status) { while(__exit_funcs!=NULL){ ... __exit_funcs = __exit_funcs->next; } ... }
以 linklist 方式 (其實也沒說一定要用 linklist,只是較適合 ) 紀錄有哪些 function 在 main 呼叫完時必須繼續執行,諸如上述的,heap、IO、global destructor 與 atexit 所註冊之 function。
2. 示範例與結果參考
#include <cstdio> #include <cstdlib> ////////////////////////////////////////////////////////////////////////// // (1) depends on compiler // < gcc only > /* before main */ __attribute__((constructor)) void before_main1(){ puts("before_main1");} __attribute__((constructor)) void before_main2(){ puts("before_main2");} __attribute__((destructor)) void after_main(){ puts("after main");} ////////////////////////////////////////////////////////////////////////// // (2) global class object constructor / destructor // < C++ only > class myclass{ public: myclass() {puts("constructor from global class");} ~myclass() {puts("destructor from global class");} }; myclass gobal_class; ////////////////////////////////////////////////////////////////////////// // (3) initialize global variable // < C++ only > int gfunc() { puts("from gfunc"); return 1; } int gvar = gfunc(); ////////////////////////////////////////////////////////////////////////// // (4) main function int main(); ////////////////////////////////////////////////////////////////////////// // (5) exit main void ex_func1() {puts("from ex_func1"); ;} void ex_func2() {puts("from ex_func2"); ;} ////////////////////////////////////////////////////////////////////////// // (4) main function int main() { puts("from main"); atexit(ex_func1); atexit(ex_func2); return 0; }
執行結果
constructor from global class
from gfunc
before_main2
before_main1
from main
from ex_func2
from ex_func1
after main
destructor from global class
結果觀查
(1) 全域類別物件建構 、全域變數初始化 -> (與 MSVC 不同 )
(2) stack 順序執行 __attribute__((constructor)) 指令函式 ->
(3) main ->
(4) stack 順序執行 atexit ->
(5) stack 順序執行 __attribute__((destructor)) 指令函式
(6) 全域類別物件解構
[3] 隱藏 console 視窗
必須在 linker 那裡動手腳,但 gcc.exe / g++.exe 這部份有提供參數,假設原始檔為 x.cpp,欲產生不會有 console window 之 x.exe 如下。
g++ -o x.exe x.cpp -mwindows
Visual C++ (MSVC)
1. 動作原理
(1) 從 mainCRTStartup() 進入 (依 Wide String 分版本)。
(2) 進入 _heap_init(),初始化 heap,完成後反回 mainCRTStartup()。
(3) 進入 _ioinit(),初始化 IO。
(4) 取得 argc, argv , env
(5) 初始化資料
(6) 呼叫 main 記錄傳回值
(7) 檢查錯誤,將 main 傳回值傳回。
2. 示例碼與結果參考
////////////////////////////////////////////////////////////////////////// // (1) depends on compiler // < MSCV only > /* before main */ #define SECNAME ".CRT$XCG" #pragma section(SECNAME, long, read) static void before_main1(){ puts("before_main1");} static void before_main2(){ puts("before_main2");} typedef void (__cdecl * _PVFV)(); __declspec( allocate(SECNAME)) _PVFV dump[] = {before_main1, before_main2}; ////////////////////////////////////////////////////////////////////////// // (2) global class object constructor / destructor // < C++ only > class myclass{ public: myclass() {puts("constructor from global class");} ~myclass() {puts("destructor from global class");} }; myclass gobal_class; ////////////////////////////////////////////////////////////////////////// // (3) initialize global variable // < C++ only > int gfunc() { puts("from gfunc"); return 1; } int gvar = gfunc(); ////////////////////////////////////////////////////////////////////////// // (4) main function int main(); ////////////////////////////////////////////////////////////////////////// // (5) exit main void ex_func1() {puts("from ex_func1"); ;} void ex_func2() {puts("from ex_func2"); ;} ////////////////////////////////////////////////////////////////////////// // (4) main function int main() { puts("from main"); atexit(ex_func1); atexit(ex_func2); return 0; }
執行結果
before_main1
before_main2
constructor from global clas
from gfunc
from main
from ex_func2
from ex_func1
destructor from global class
結果觀查
(1) 依序執行 #pragma section(".CRT$XCG", long, read) 指令函式 ->
(2) 全域類別物件建構 、全域變數初始化 ->
(3) main ->
(4) stack 順序執行 atexit ->
(5) 全域類別物件解構
[3] 隱藏 main
這點在 VC 算是常見到的。console 若不想顯示出視窗本體,可由 linker 那裡下手
以我手邊 VC2010 為例,是放在
「專案」-> 「屬性」-> 「連結器」-> 「命令列」,
在下面之「其他選擇項」輸入 "/subsystem:windows /entry:mainCRTStartup" 即可。
也可能程式碼開頭加入這行
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
是一樣的意思。
before_main portable in C ?
上述不論是 glibc, msvc,扣除掉 depends on compiler 部份,尷尬的是沒有一個方法可用於 C language (都在 C++ 下才能用),真要達成可攜作法,除了一一研究各家 linker 外,似乎沒其他作法。有種聲稱 kuso 作法是用 macro ,但實際上只是把 main 定義為 second_main ,而在其他 header 再寫個 main 而已,實際上經過 preprocessor 後還是一樣從 main 進入。
最後,可以確定幾件事只有,關於 entry,只能確定某些步驟比 main 早,某些比 main 晚,但詳細之 order 關係並無一定之關係,所有相對之執行點 compiler 均保留實作之權力 (甚至一個複雜一點的問題:在 global class object 裡,註冊一個 atexit 函式又會變怎樣?沒用 compiler 試過真的不知道.. )