C++跨DLL記憶體所有權問題探幽(二)CRT中MT和MD混用導致的堆損壞

軒先生。發表於2023-11-22

0xC0000374: 堆已損壞。 (引數: 0x00007FFA1E9787F0)。
_Mem 是 nullptr

我在開發的過程中有遇到上面兩個東西的bug,百思不得其解,最後才發現這個和兩個DLL中的MT和 MD選項有關係。

具體情境時:我在一個MT編譯的DLL A中引用了一個MD編譯的DLL B,並且在A的標頭檔案中宣告瞭一個B物件,這段程式碼在使用的過程中產生了所有權問題,導致了上述的兩個問題。

在正式討論這個問題之前,需要做一些知識儲備

什麼是MD和MT編譯?

在C++中,MD(Multi-threaded DLL)和MT(Multi-threaded)是Microsoft Visual C++編譯器提供的不同的執行時庫選項。它們在處理執行緒、記憶體管理和連結方式上有所不同。

Multi-threaded DLL(MD):

相當於在編譯的時候不將DLL依賴的DLL放在其內部。

MD選項意味著您的應用程式將使用動態連結的多執行緒C/C++執行時庫(CRT)。這意味著您的應用程式將與系統共享這些執行時庫。這可以減少最終生成的可執行檔案的大小,因為它們不會包含整個執行時庫的副本。

執行時庫的版本由作業系統決定。如果系統中已經安裝了相應版本的執行時庫,那麼您的應用程式將可以共享這些庫,而不需要額外的安裝。

Multi-threaded(MT):

相當於在編譯的時候將DLL依賴的DLL放在其內部。

MT選項意味著您的應用程式將使用靜態連結的多執行緒C/C++執行時庫(CRT)。這意味著您的應用程式將包含完整的執行時庫的副本,因此可能會增加最終生成的可執行檔案的大小。
執行時庫會隨著應用程式一起分發,因此使用者在執行應用程式之前不需要安裝任何其他元件。
這些執行時庫負責處理諸如記憶體管理、執行緒管理、異常處理和其他與C/C++程式設計相關的任務。它們提供了諸如動態記憶體分配和釋放、執行緒同步機制、異常處理等功能。選擇使用哪種執行時庫取決於專案的需求,以及對最終可執行檔案大小和依賴性的要求。選擇不同的執行時庫可能會影響應用程式的效能和行為。

關於DLL引用

書接上文C++跨DLL記憶體所有權問題探幽(一)DLL提供的全域性單例模式
我們知道一個程式有堆疊啊這些記憶體空間。

在C++開發中,堆空間和棧空間是用來儲存變數和物件的兩個主要記憶體區域。當一個程式引用一個DLL(動態連結庫)時,在標頭檔案中宣告一個物件和宣告一個指標有一些關鍵區別:

  1. 物件宣告:

如果您在標頭檔案中宣告一個物件,它將分配在棧空間中。這意味著物件的生命週期將受限於其所在的作用域。當物件所在的作用域結束時,物件將被自動銷燬並釋放其佔用的記憶體。

如果物件是在動態連結庫中定義的,那麼在引用動態連結庫的程式中,物件的定義和實現將被複制到主程式中。這可能會導致重複定義的問題。

  1. 指標宣告:

如果您在標頭檔案中宣告一個指標,它將分配在棧空間中。但是指標所指向的物件可能分配在堆空間中,特別是如果您在動態連結庫中使用new關鍵字來動態分配記憶體。

透過使用指標,您可以在程式中傳遞物件的引用而不是實際的物件本身。這使得物件可以在堆上動態分配,並且可以在不同的模組之間共享。

也就是說

為什麼崩潰?

當在C++中混用MD(Multi-threaded DLL)和MT(Multi-threaded)的DLL時,可能會導致記憶體衝突和崩潰的主要原因在於堆疊空間的所有權問題。

對堆空間而言

對於MD編譯的DLL,它使用的是共享的動態連結的多執行緒C/C++執行時庫,這意味著它使用了作業系統提供的堆管理機制來分配和釋放記憶體。如果您在MD編譯的DLL中分配了一塊堆記憶體,它實際上是由作業系統的執行時庫進行管理的。

對於MT編譯的DLL,它使用的是靜態連結的多執行緒C/C++執行時庫,這意味著它會包含自己的堆管理機制。如果您在MT編譯的DLL中分配了一塊堆記憶體,它將由該DLL的執行時庫管理。

棧空間:

棧空間的所有權歸屬於當前執行緒。當您在MD和MT編譯的DLL之間切換時,棧空間的所有權可能會發生變化。如果一個執行緒在MD編譯的DLL中分配了一塊棧記憶體,然後在MT編譯的DLL中嘗試釋放它,或者反之亦然,就會產生記憶體衝突,導致不可預測的行為和可能的崩潰。

因此,在混用MD和MT編譯的DLL時,由於堆空間和棧空間的所有權歸屬和管理方式不同,可能會導致記憶體的衝突。這種衝突可能會引起一系列問題,包括記憶體洩漏、指標懸空、資料損壞等,最終導致程式崩潰或產生不可預測的行為。為避免這種情況,請確保在整個應用程式中使用相同型別的執行時庫編譯所有的DLL。

參考:MD(d)、MT(d)編譯選項,在使用Release編譯的話不會觸發這個崩潰問題。

相關文章