看板 C_and_CPP 關於我們 聯絡資訊
※ 引述《Favonia (小西風最乖了*^^*)》之銘言: : ※ 引述《tropical72 (藍影)》之銘言: : : 一開始一直試都不成功,原因在於 Visual C++ .c 不支援 C99, : : 改 .cpp 之後就過了,一開始查 msdn ,查到這份網頁 : : http://msdn.microsoft.com/en-us/library/bb918180.aspx : : 看完第一個 global variable initialize 後,裡面說明提到 : : According to the C/C++ standard, func() must be called before main() is : : executed. But who calls it? : : ( M$ 真是自打嘴巴,明明說標準可以這麼做,偏偏 .c 連這點都不支援) : : 這部份可能查 C99 會有更多的說明, 但 M$ 這份我覺得寫得還不錯。 : (其他恕刪)因為要講的東西好像還不少,回一篇文章好了。 : (1) 首先 MSVC++ 不算做錯,因為 C99 (N1256) 禁止這語法而且編譯 : 器有責任發出錯誤訊息。標準說 : | All the expressions in an initializer for an object that has static : | storage duration shall be constant expressions or string literals. : @ C99 (N1256) 6.7.8p4 : 俗稱的全域變數符合這條,所以本來就不能呼叫函數。MSDN 寫 : C/C++ 可能只是偷懶而已 xD : (2) C++ 允許更多初始化這些變數的方法,也有稍微複雜的初始化順序 : 規則。變得比較「方便」的代價就是比較容易寫出錯誤的程式碼。 : 我記得 Google 的 C++ 寫作風格有針對 static 做嚴格規定 xD 雖然沒有碰過需要了解 main 之前的情況,但是剛剛追了一下,main 之前的順序大概是這樣: 從 crtexe.c:mainCRTStartup (我用檔案:函式表示)開始,裏面呼叫 gs_support.c:__security_init_cookie 去設定 security cookie,這是 VC++ 裏面保護堆疊的機制,預設編譯的參數是打開的(/GS),然後 gs_support.c 離開回來到 mainCRTStartup 內部,接下去再呼叫 crtexe.c:__tmainCRTStartup,重點都是在這個函式內部。 繼續之前,先釐清為何程式會知道從 crtexe.c:mainCRTStartup 開始,在 EXE 檔案的 PE 表頭定義了程式的進入點(entry point),從 entry point 去看會發現 VC++ 把許多起始的函式,例如上面說的 mainCRTStartup、以及其他系統函式的進入點(jump point, 我自己稱呼的名字),都像壓縮一樣擠在 entry point 附近,而 PE 表頭定義的 entry point 位置,就是 mainCRTStartup 的 jump point,例如: PE 表頭定義 entry point 位置為 0x00411104,而指標 0x00411104 的內容為 \xE9\xF7\x06\x00\x00,這是 OPCODE,其意義代表是程式跳到 0x00411109+0x6F7,也就是跳到 0x00411800 的位置,去 0x0041800 看會發現是跳到 mainCRTStartup 裏面,依此類推。 結論就是 EXE 檔案的表頭定義了程式的 entry point,從進入點會開始發生程式的起始行為。 回到 crtexe.c:__tmainCRTStartup,裏面呼叫 GetStartupInfoW (這個函式裏面會去取得 process,thread, ... 一些資訊,這裡不是重點,所以不追下去),然後呼叫 HeapSetInformation 去設定一些 heap 的東西(大概吧,同樣不是重點,不追),然後程式會去作一些 runtime startup 的事(根據 crtexe.c 的註解說詞是這樣),這裡是重點,等會解釋, 然後 __tmainCRTStartup 接下去 parse command line,最後呼叫 main 或者是 wmain,所以 main 函式是在這裡被執行,從這樣的順序可以看到 main 函式被呼叫前做了哪些動作。 本篇重點是在 runtime startup 那邊,這裡做了一系列的事,但是我們只專注在原 PO 的兩段程式碼,第一段程式碼透過函式指標去運作,Alloc10 是在 crtexe.c 的第 473 行,就是函式 _initterm 裏面執行,_initterm 沒有程式碼,所以不知道裏面做什麼,MSDN 有簡略的解釋這個函式,但似乎沒有太大的幫助。 第二段程式碼我在 VC++ 2010 Express 上面跑也跑不進 main 裏面,原因是因為 before_main 函式是在 crtexe.c 的第 455 行,就是函式 _initterm_e 裏面執行,同樣,_initterm_e 也沒有程式碼,去看 assembly 可以發現它會按順序去執行一些函式指標,而且會檢查每個函式指標執行完之後的回傳值,如果回傳非零,代表執行失敗,程式立刻結束,會呼叫 ExitThread 離開程式,before_main 就是在這邊被呼叫的,而在 VC++ 2010 跑不進 main 的原因,是因為 before_main 的回傳值是 0xCCCCCCCC,因為 before_main 宣告回傳是 void 型別,所以把 before_main 宣告為 int 並且回傳 0 即可,如下: int before_main(void); /* function declare */ typedef int ( *pfpuc)(void); /* function pointer */ // ...(省略) int before_main(void) { z = 100; return 0; } 大概是這樣,如果 VC++ 版本不同,或者 msvcr***.dll 不同,應該也會有差異,因為 _initterm_e/_initterm 是在 msvcr***.dll 裏面的(在VC++ 2010 Debug 版本下,是呼叫 msvcr100d.dll),不過有點想問為什麼需要知道 main 之前執行了哪些東西,用途在哪呢? 謝謝。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 111.249.189.71
purpose:好多 \ 貼文章還是用 PCMan 比較好用 09/04 12:11
fon909:PCMan 是否有支援SSH? 我需要用SSH連網:ssh bbs@ptt.cc 09/04 12:14
purpose:我用的這個 PCMan 9.15 是沒有,其他相關分支就不知道了 09/04 12:18
tropical72:我只能說,我trace到一份不是很好的code,他用了 09/04 13:48
tropical72:int garbge=init(); 裡面做大量 malloc, init 是放 09/04 13:48
tropical72:在 xxxx.c 裡面的 static func, 所以讓我疑惑到他記憶 09/04 13:49
tropical72:體管理怎那麼奇怪。最後,感謝您的解決,受益良多。:) 09/04 13:49
fon909:@purpose:我下次會留意word wrapping,我用gnome-terminal:) 09/04 14:32
fon909:@tropical72:我自己在過程中也更熟悉VC編譯的程式的運作:) 09/04 14:34