2012-08-31 37 views
1

我在Jeffrey Richter和Christophe Nasarre的C++中,通過Windows的書中的筆記發現了這個問題。當與靜態c運行時鏈接時的內存分配/釋放問題

檢查下面的代碼: V

OID EXEFunc() { 
PVOID pv = DLLFunc(); 
// Access the storage pointed to by pv... 
// Assumes that pv is in EXE's C/C++ run-time heap 
free(pv); 
} 
PVOID DLLFunc() { 
// Allocate block from DLL's C/C++ run-time heap 
return(malloc(100)); 
} 

所以,你有什麼感想?前面的代碼是否正確工作?由EXE的函數釋放的 DLL函數分配的塊是否被分配?答案是:也許。顯示的代碼不是 會給你足夠的信息。如果EXE和DLL鏈接到DLL C/C++運行庫 庫,代碼工作得很好。但是,如果其中一個或兩個模塊鏈接到靜態C/C++運行時庫,則釋放調用失敗。

我無法理解,在將模塊鏈接到靜態C運行庫時,爲什麼免費調用會失敗。

有人可以解釋爲什麼免費失敗? 發現類似的問題在這裏: Memory Allocation in Static vs Dynamic Linking of C Runtime

但我在這裏已經同疑問MrPhilTx: 難道所有的堆在同一個地址空間?

謝謝!

回答

4

當您的DLL和EXE都靜態鏈接到C運行時,兩個運行時間根本不知道彼此。因此,EXE和DLL都可以獲得他們自己的運行時副本,他們自己的堆和堆元數據。任何一方都不知道其他元數據,並且在釋放內存時沒有安全的方式來更新數據。你最終會得到不定的元數據,事情最終會失敗(如果你非常幸運,它會馬上失敗)。

這意味着你最終在你的過程中至少有兩堆,每個堆都有自己的規則和元數據。 EXE無法知道DLL分配內存的確切方式,因此無法釋放它。

至於爲什麼你可以在一切都動態鏈接時共享一個堆,這很容易,在這個過程中只有一個C運行時DLL副本,所以如果每個DLL鏈接它,它們都會調用具有相同元數據的相同代碼。

+1

即使鏈接到EXE和DLL的運行時代碼是相同的,但存在單獨的元數據副本這一事實會攪亂一切。分配在一堆並釋放到另一堆會導致一個大混亂。 –

2

您不能從一個分配器分配內存並將其與另一個分配器釋放。不同的分配器使用不同的內部實現,並且給分配器分配內存塊的結果是不可預知的。

所以,除非您知道兩段代碼使用相同的分配器,否則您不能在一段代碼中分配內存,並將其釋放到另一段中。通常的解決方案是確保同一個單元既分配又釋放內存。在你的例子中,DLL可以提供主代碼可以調用的「自由」功能,而不是調用自己的free函數,該函數釋放給它自己的分配器。

所以做這個:

OID EXEFunc() { 
    PVOID pv = DLLFunc(); 
    // Access the storage pointed to by pv... 
    // Assumes that pv is in EXE's C/C++ run-time heap 
    DLLFreeFunc(pv); 
} 

... 

PVOID DLLFunc() { 
    // Allocate block from DLL's C/C++ run-time heap 
    return(malloc(100)); 
} 

DLLFreeFunc(PVOID x) { 
    free(x); 
} 
0

代碼關鍵取決於mallocfree實施。一個好的實現沒有問題,一個糟糕的實際上會失敗。創建一個工作的DLL實現mallocfree肯定更容易,但在靜態庫中這樣做是不可能的。

一個簡單的例子是一個靜態庫,它將呼叫直接轉發到GlobalAllocGlobalFree

+0

你的意思是在這裏出現問題,因爲EXE和DLL可能已經鏈接到兩個不同的malloc/free實現? – Suhas

+0

@素:不是。有兩種不同的實現本身就很麻煩,不管你是在2個DLL還是2個靜態庫中都有。 – MSalters

1

在Linux上,程序使用brk和sbrk系統調用從內核請求額外的數據頁面。 sbrk返回一個指向可以被程序使用的數據段的地址。

malloc和free將brk和sbrk返回的數據段變成堆。堆是當前存儲空間中的一大塊內存,可根據需要請求並返回小塊內存。請注意,很多malloc和free的調用都不會進行系統調用。

現在,當malloc和free可以使用堆時,他們需要獲得一個指向堆的指針。該指針存儲在一個名爲靜態數據的獨立數據段中,並在應用程序加載時分配。爲了確保不同的DLL(或Linux上的共享庫)與彼此不衝突,每個DLL都有自己的靜態數據部分。

現在讓我們假設dll和可執行文件都靜態鏈接到它們自己的庫。在這種情況下,dll和可執行文件將指向不同的堆,並且是這樣的事件,dll和可執行文件都必須釋放自己的內存。

但是在Linux上,dll和可執行文件都將通過一個共同的DLL(linux上的libc.so)訪問malloc和free。在這種情況下,由於dll和可執行文件都有效地訪問libc的堆,可執行文件可以安全地釋放由dll分配的內存。

在任何情況下,爲dll提供自己的免費功能是一個好習慣。這個如果沒有其他的說明DLLFunc返回的指針需要被釋放。

我想這在Windows上也是如此。

+0

由於將CRT靜態鏈接到共享庫和EXE,您能否詳細說明或共享處於不同堆的一些鏈接?我找不到很多信息。每個進程只有一個堆的地方。 –