C++跨DLL内存所有权问题探幽(二)CRT中MT和MD混用导致的堆损坏
0xC0000374: 堆已損壞。 (參數(shù): 0x00007FFA1E9787F0)。
_Mem 是 nullptr
我在開發(fā)的過(guò)程中有遇到上面兩個(gè)東西的bug,百思不得其解,最后才發(fā)現(xiàn)這個(gè)和兩個(gè)DLL中的MT和 MD選項(xiàng)有關(guān)系。
具體情境時(shí):我在一個(gè)MT編譯的DLL A中引用了一個(gè)MD編譯的DLL B,并且在A的頭文件中聲明了一個(gè)B對(duì)象,這段代碼在使用的過(guò)程中產(chǎn)生了所有權(quán)問(wèn)題,導(dǎo)致了上述的兩個(gè)問(wèn)題。
在正式討論這個(gè)問(wèn)題之前,需要做一些知識(shí)儲(chǔ)備
什么是MD和MT編譯?
在C++中,MD(Multi-threaded DLL)和MT(Multi-threaded)是Microsoft Visual C++編譯器提供的不同的運(yùn)行時(shí)庫(kù)選項(xiàng)。它們?cè)谔幚砭€程、內(nèi)存管理和鏈接方式上有所不同。
Multi-threaded DLL(MD):
相當(dāng)于在編譯的時(shí)候不將DLL依賴的DLL放在其內(nèi)部。
MD選項(xiàng)意味著您的應(yīng)用程序?qū)⑹褂脛?dòng)態(tài)鏈接的多線程C/C++運(yùn)行時(shí)庫(kù)(CRT)。這意味著您的應(yīng)用程序?qū)⑴c系統(tǒng)共享這些運(yùn)行時(shí)庫(kù)。這可以減少最終生成的可執(zhí)行文件的大小,因?yàn)樗鼈儾粫?huì)包含整個(gè)運(yùn)行時(shí)庫(kù)的副本。
運(yùn)行時(shí)庫(kù)的版本由操作系統(tǒng)決定。如果系統(tǒng)中已經(jīng)安裝了相應(yīng)版本的運(yùn)行時(shí)庫(kù),那么您的應(yīng)用程序?qū)⒖梢怨蚕磉@些庫(kù),而不需要額外的安裝。
Multi-threaded(MT):
相當(dāng)于在編譯的時(shí)候?qū)LL依賴的DLL放在其內(nèi)部。
MT選項(xiàng)意味著您的應(yīng)用程序?qū)⑹褂渺o態(tài)鏈接的多線程C/C++運(yùn)行時(shí)庫(kù)(CRT)。這意味著您的應(yīng)用程序?qū)暾倪\(yùn)行時(shí)庫(kù)的副本,因此可能會(huì)增加最終生成的可執(zhí)行文件的大小。
運(yùn)行時(shí)庫(kù)會(huì)隨著應(yīng)用程序一起分發(fā),因此用戶在運(yùn)行應(yīng)用程序之前不需要安裝任何其他組件。
這些運(yùn)行時(shí)庫(kù)負(fù)責(zé)處理諸如內(nèi)存管理、線程管理、異常處理和其他與C/C++編程相關(guān)的任務(wù)。它們提供了諸如動(dòng)態(tài)內(nèi)存分配和釋放、線程同步機(jī)制、異常處理等功能。選擇使用哪種運(yùn)行時(shí)庫(kù)取決于項(xiàng)目的需求,以及對(duì)最終可執(zhí)行文件大小和依賴性的要求。選擇不同的運(yùn)行時(shí)庫(kù)可能會(huì)影響應(yīng)用程序的性能和行為。
關(guān)于DLL引用
書接上文C++跨DLL內(nèi)存所有權(quán)問(wèn)題探幽(一)DLL提供的全局單例模式
我們知道一個(gè)程序有堆棧啊這些內(nèi)存空間。
在C++開發(fā)中,堆空間和棧空間是用來(lái)存儲(chǔ)變量和對(duì)象的兩個(gè)主要內(nèi)存區(qū)域。當(dāng)一個(gè)進(jìn)程引用一個(gè)DLL(動(dòng)態(tài)鏈接庫(kù))時(shí),在頭文件中聲明一個(gè)對(duì)象和聲明一個(gè)指針有一些關(guān)鍵區(qū)別:
- 對(duì)象聲明:
如果您在頭文件中聲明一個(gè)對(duì)象,它將分配在棧空間中。這意味著對(duì)象的生命周期將受限于其所在的作用域。當(dāng)對(duì)象所在的作用域結(jié)束時(shí),對(duì)象將被自動(dòng)銷毀并釋放其占用的內(nèi)存。
如果對(duì)象是在動(dòng)態(tài)鏈接庫(kù)中定義的,那么在引用動(dòng)態(tài)鏈接庫(kù)的程序中,對(duì)象的定義和實(shí)現(xiàn)將被復(fù)制到主程序中。這可能會(huì)導(dǎo)致重復(fù)定義的問(wèn)題。
- 指針聲明:
如果您在頭文件中聲明一個(gè)指針,它將分配在棧空間中。但是指針?biāo)赶虻膶?duì)象可能分配在堆空間中,特別是如果您在動(dòng)態(tài)鏈接庫(kù)中使用new關(guān)鍵字來(lái)動(dòng)態(tài)分配內(nèi)存。
通過(guò)使用指針,您可以在程序中傳遞對(duì)象的引用而不是實(shí)際的對(duì)象本身。這使得對(duì)象可以在堆上動(dòng)態(tài)分配,并且可以在不同的模塊之間共享。
也就是說(shuō)
為什么崩潰?
當(dāng)在C++中混用MD(Multi-threaded DLL)和MT(Multi-threaded)的DLL時(shí),可能會(huì)導(dǎo)致內(nèi)存沖突和崩潰的主要原因在于堆棧空間的所有權(quán)問(wèn)題。
對(duì)堆空間而言
對(duì)于MD編譯的DLL,它使用的是共享的動(dòng)態(tài)鏈接的多線程C/C++運(yùn)行時(shí)庫(kù),這意味著它使用了操作系統(tǒng)提供的堆管理機(jī)制來(lái)分配和釋放內(nèi)存。如果您在MD編譯的DLL中分配了一塊堆內(nèi)存,它實(shí)際上是由操作系統(tǒng)的運(yùn)行時(shí)庫(kù)進(jìn)行管理的。
對(duì)于MT編譯的DLL,它使用的是靜態(tài)鏈接的多線程C/C++運(yùn)行時(shí)庫(kù),這意味著它會(huì)包含自己的堆管理機(jī)制。如果您在MT編譯的DLL中分配了一塊堆內(nèi)存,它將由該DLL的運(yùn)行時(shí)庫(kù)管理。
棧空間:
棧空間的所有權(quán)歸屬于當(dāng)前線程。當(dāng)您在MD和MT編譯的DLL之間切換時(shí),棧空間的所有權(quán)可能會(huì)發(fā)生變化。如果一個(gè)線程在MD編譯的DLL中分配了一塊棧內(nèi)存,然后在MT編譯的DLL中嘗試釋放它,或者反之亦然,就會(huì)產(chǎn)生內(nèi)存沖突,導(dǎo)致不可預(yù)測(cè)的行為和可能的崩潰。
因此,在混用MD和MT編譯的DLL時(shí),由于堆空間和棧空間的所有權(quán)歸屬和管理方式不同,可能會(huì)導(dǎo)致內(nèi)存的沖突。這種沖突可能會(huì)引起一系列問(wèn)題,包括內(nèi)存泄漏、指針懸空、數(shù)據(jù)損壞等,最終導(dǎo)致程序崩潰或產(chǎn)生不可預(yù)測(cè)的行為。為避免這種情況,請(qǐng)確保在整個(gè)應(yīng)用程序中使用相同類型的運(yùn)行時(shí)庫(kù)編譯所有的DLL。
參考:MD(d)、MT(d)編譯選項(xiàng),在使用Release編譯的話不會(huì)觸發(fā)這個(gè)崩潰問(wèn)題。
總結(jié)
以上是生活随笔為你收集整理的C++跨DLL内存所有权问题探幽(二)CRT中MT和MD混用导致的堆损坏的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Rs1805007影响人体对麻醉药的敏感
- 下一篇: 鸡蛋炒鱼蛋怎么做好吃呢?