1. 程式人生 > >讀書筆記_執行庫的初始化和清理

讀書筆記_執行庫的初始化和清理

 

執行庫的初始化和清理

編譯器會為每個模組自動插入一個“編譯器編寫”的入口函式,在這個入口函式中進行好各種初始化工作後再呼叫使用者的入口函式,在使用者的入口函式返回後在執行自己的清理函式,我們把編譯器插入的這個入口函式稱為CRT入口函式

Exe模組的入口函式

使用者入口函式

CRT入口函式

應用

main

mainCRTStartup

控制檯程式

wmain

wmainCRTStartup

寬字元的控制檯程式

WinMain

WinMainCRTStartup

Win32應用程式

wWinMain

wWinMainCRTStartup

寬字元的Win32應用程式

DLL模組的使用者入口函式是DllMain,因為DllMain沒有字元型別的引數,所以對於寬字元(Unicode)的DLL模組,其入口函式也是DllMain。

         編譯器為DLL模組準備的CRT入口函式是_DllMainCRTStartup函式,其函式原型與DllMain完全相同。

在連結階段,模組的入口函式是被註冊到目標程式檔案(PE檔案)中的,預設情況下,連結器會將CRT入口函式註冊為模組入口,這樣在執行的過程中首先執行的是CRT的入口函式,對CRT進行初始化的,等使用者的入口函式返回後,CRT的入口函式可以執行清理工作。

此外,連結器支援通過引數 /ENTRY: function選項指定其他函式作為入口。如果在專案連結屬性中(output)將Entry-point symbol 指定為WinMain函式名,或者直接在命令列引數中加入/ENTRY: “WinMain”, 那麼就將WinMain函式設定為程式的入口了。此時就必須要使用者自己考慮如何初始化和清理執行庫了。

下面來看以一個程序中的多個模組都使用執行庫的情況。對於靜態連結執行庫德程式模組,不論是EXE還是DLL,每個模組內部都複製了一份執行庫的變數和程式碼,這樣每個模組中都有一個執行庫的例項。當程序中有多個執行庫例項時,每個執行庫例項會各自使用自己的資料(變數)和資源(堆),理論上是可以正常工作的。但有時也可能引發問題,其中之一就是從一個CRT堆分配的記憶體被送給另一個CRT例項來釋放,在除錯版本中CRT的記憶體檢查功能會發現這一問題並報告錯誤。但在釋出版本中,這將會導致嚴重的問題。

執行檢查是指執行期間對其進行的各種檢查,主要是為了發現執行時所暴露的各種錯誤, run-time errors。編譯器通常採取如下幾種措施

1.  使用除錯版本的支援庫和庫函式,除錯版本的庫函式包含了更多的除錯支援和檢查功能。比如除錯版本的記憶體分配和釋放函式會插入額外的資訊來支援各種記憶體檢查功能。

2.  在編譯時插入額外程式碼對棧指標、區域性變數等進行檢查。

3.  提供斷言(ASSERT),報告(_RPT)等機制讓程式設計師在編寫程式時加入檢查程式碼和報告執行期錯誤。

前兩種是編譯器行為,第三種需要手動插入。

下面以VC8的編譯器為例,來看自動執行期檢查能夠發現的執行期錯誤:

1.  棧指標被破壞(Stack pointer corruption),負責棧指標檢查的函式是_RTC_CheckEsp.

2.  分配在棧上的區域性變數越界(Overruns),以及因此而導致的棧被破壞(Stack corruption)

3.  依賴未初始化的區域性變數,負責該功能的RTC函式是_RTC_UninitUse,如果程式中使用了沒有初始化的區域性變數,那麼編譯器在編譯期會給出C4700或C4701號警告,但這兩個警告屬於第4級別的警告(Level 4 Warning),所以有可能被遮蔽掉或被忽視。如果啟用了/RTCu開關,當程式執行到使用未初始化過的區域性變數時,會彈出錯誤報告對話方塊。

4.  因為賦值給較短的變數而導致資料丟失,負責該項檢查的RTC函式是_RTC_Check_x_to_y,其中x和y可以是8,4,2,1幾個值的組合。

5.  使用堆有關的錯誤。

關於斷言,VC編譯器的斷言是_ASSERT和_ASSERTE,MFC框架定義了ASSERT巨集和VERIFY巨集,標準C中也提供了一個assert(全小寫)的巨集,使用方法基本相同。

當發現錯誤後,主要通過函式_CrtDbgReport進行報告,斷言失敗和_RPT巨集都是通過呼叫_CrtDbgReport函式報告除錯資訊的。