Windows 下主程序與動態庫(*.dll)釋放對方分配的內存操作要點

同樣的代碼程序:
主程序中釋放了一塊在 動態庫(*.dll)或共享庫(*.so) 中分配的內存,
Windows 將會出現程序崩潰,而 Linux 則正常運行。

在 linux 下,每個進程只有一個 heap ,
在任何一個共享庫模塊 *.so 中通過 new 或者 malloc 來分配內存的時候都是從這個唯一的 heap 中分配的,
那麼自然你在其它什麼地方都可以釋放。

但是 windows 下面確不是如此:
1. windows 允許一個進程中有多個 heap ,那麼這樣就需要指明一塊內存要在哪個 heap 上分配,
   win32 的 HeapAlloc 函數就是這樣設計的,
   給出一個 heap 的句柄、一個可選的分配操作標誌、一個字節塊大小,然後返回一個指針。
   每個進程都至少有一個主 heap ,它的句柄可以通過 GetProcessHeap 來獲得,
   其它的堆句柄可以通過 GetProcessHeaps 取到。
   同樣,內存釋放的時候通過 HeapFree 來完成,還是需要指定一個堆句柄。

2. 這樣的設計顯然是比較靈活的,
   但是問題在於這樣的話,每次分配內存的時候就必須要顯式的指定一個 heap 句柄,
   對於 crt 中的 new/malloc ,顯然需要特殊處理。
   那麼如何處理就取決於 crt 的實現了。
   VC++ 的 crt 是創建了一個單獨的 heap,叫做 __crtheap ,它對於用戶是看不見的,
   但是在 new/malloc 的實現中,都是用 HeapAlloc 在這個 __crtheap 堆上分配的,
   也就是說 malloc(size) 基本上可以認爲等同於 HeapAlloc(__crtheap, size)
   (當然實際上 crt 內部還要維護一些內存管理的數據結構,
    所以並不是每次 malloc 都必然會觸發 HeapAlloc ),
   這樣 new/malloc 就和 windows 的 heap 機制吻合了。
   (這裏說的是 VC 的 crt 實現,我不知道其它 crt 實現是否如此)

3. 如果一個進程需要動態庫支持,系統在加載 dll 的時候,在 dll 的啓動代碼 _DllMainCRTStartup 中,
   會創建這個 __crtheap ,所以理論上有多少個 dll,就有多少個 __crtheap 。
   最後主進程的 mainCRTStartup 中還會創建一個爲主進程服務的 __crtheap 。
   (由於順序總是先加載 dll ,然後才啓動 main 進程,
    所以你可以看到各個 dll 的 __crtheap 地址比較小,
    而主進程的 __crtheap 地址比較大,當然排在最前面的堆是每個進程的主 heap 。)

4. 從上面的分析中可以看出,對於 crt 來說,由於每個 dll 都有自己的 heap ,
   所以每個 dll 通過 new/malloc 分配的內存都是在自己 dll 內部的那個 heap 上用 HeapAlloc 來分配的,
   而如果你想在其它模塊中釋放,那麼在釋放的時候 HeapFree 就會失敗了,
   因爲各個模塊的 __crtheap 是不一樣的。

事情基本清楚了,在 windows 下一個進程存在着多個 heap ,
除了一個主 heap 外,還有很多的 __crtheap ,用來處理通過 C/C++ 的運行庫進行的內存操作。
所以使用 new/malloc 來分配的內存實際上都是局部的,可以在多個 dll 中共享,
但是卻必須是誰申請誰釋放。

這個是 Windows 下的一個規則。以前知道這個規則,但是不知道爲什麼,現在算是比較明白了。
如果在 dll 內部使用 HeapAlloc(GetProcessHeap(), 0, 字節塊大小 ) 來分配的內存,
是可以在 dll 以外釋放的,
因爲這時內存分配在全局的主 heap 上,而不是分配在 dll 自己的 __crtheap 上。

轉自 http://blog.sina.com.cn/s/blog_4c451e0e0100u9gu.html
發佈了171 篇原創文章 · 獲贊 68 · 訪問量 229萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章