C/C++跨模組釋放記憶體
在linux下跑得一直很好的程式,到了windows下面就跑不起來了。記憶體異常,檢查了一下,很快發現是因為在主程式中釋放了一塊在DLL中分配的記憶體,這種問題雖然早就知道了,但是一直沒有仔細考慮過,所以今天就深入研究了一下。
在linux下,每個程序只有一個heap,在任何一個動態庫模組so中通過new或者malloc來分配記憶體的時候都是從這個唯一的heap中分配的,那麼自然你在其它隨便什麼地方都可以釋放。這個模型是簡單的。
但是在windows下面,問題變得複雜了。
1、windows允許一個程序中有多個heap,那麼這樣就需要指明一塊記憶體要在哪個heap上分配,win32的HeapAlloc函式就是這樣設計的,給出一個heap的控制代碼,給出一個size,然後返回一個指標。每個程序都至少有一個主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(), size)來分配的記憶體是可以在dll以外釋放的,因為這時記憶體分配在全域性的主heap上,而不是分配在dll自己的__crtheap上)