欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析C++如何跨模塊釋放內(nèi)存

 更新時(shí)間:2024年02月23日 10:26:26   作者:Simple?Simple  
這篇文章主要為大家詳細(xì)介紹了C++中跨模塊釋放內(nèi)存的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以了解下

在開(kāi)發(fā)主程序和動(dòng)態(tài)庫(kù)時(shí),首要原則就是:避免跨模塊申請(qǐng)和釋放內(nèi)存。這一點(diǎn),我們?cè)诤芏嚅_(kāi)源庫(kù)或者平常項(xiàng)目中也都碰到過(guò),對(duì)于動(dòng)態(tài)庫(kù)中的堆內(nèi)存申請(qǐng)與釋放,動(dòng)態(tài)庫(kù)總是會(huì)提供兩個(gè)接口分別實(shí)現(xiàn)new和delete操作,而不會(huì)讓調(diào)用方自己去操作。但有時(shí)候如果違背了這個(gè)原則呢,在linux平臺(tái)上不會(huì)存在這樣的憂慮,因?yàn)樵趌inux下,每個(gè)進(jìn)程只有一個(gè)heap,在任何一個(gè)動(dòng)態(tài)庫(kù)模塊so中通過(guò)new或者malloc來(lái)分配內(nèi)存的時(shí)候都是從這個(gè)唯一的heap中分配的,那么自然你在其它隨便什么地方釋放都是沒(méi)問(wèn)題的。這個(gè)模型是簡(jiǎn)單的。而windows下就變得復(fù)雜了,下面主要介紹一下windows下的主程序和dll之間跨模塊內(nèi)存釋放的問(wèn)題。

windows允許一個(gè)進(jìn)程中有多個(gè)heap,那么當(dāng)需要在堆上分配內(nèi)存時(shí)就要指明在哪個(gè)heap上分配,win32提供了HeapAlloc函數(shù)可以在指定的堆上分配內(nèi)存。這樣的設(shè)計(jì)雖然比較靈活,但是問(wèn)題在于,每次分配內(nèi)存的時(shí)候就必須要顯式的指定一個(gè)heap,對(duì)于crt中的new/malloc,顯然需要特殊處理。那么如何處理就取決于crt的實(shí)現(xiàn)了。vc的crt是創(chuàng)建了一個(gè)單獨(dú)的heap,叫做__crtheap,它對(duì)于用戶是看不見(jiàn)的,但是在new/malloc的實(shí)現(xiàn)中,都是用HeapAlloc在這個(gè)__crtheap上分配的,也就是說(shuō)malloc(size)基本上可以認(rèn)為等同于HeapAlloc(__crtheap, size)(當(dāng)然實(shí)際上crt內(nèi)部還要維護(hù)一些內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu),所以并不是每次malloc都必然會(huì)觸發(fā)HeapAlloc),這樣new/malloc就和windows的heap機(jī)制吻合了。

如果一個(gè)進(jìn)程需要?jiǎng)討B(tài)庫(kù)支持,系統(tǒng)在加載dll的時(shí)候,在dll的啟動(dòng)代碼_DllMainCRTStartup中,會(huì)創(chuàng)建這個(gè)__crtheap,所以理論上有多少個(gè)dll,就有多少個(gè)__crtheap。最后主進(jìn)程的mainCRTStartup 中還會(huì)創(chuàng)建一個(gè)為主進(jìn)程服務(wù)的__crtheap。(由于順序總是先加載dll,然后才啟動(dòng)main進(jìn)程,所以你可以看到各個(gè)dll的__crtheap地址比較小,而主進(jìn)程的__crtheap比較大,當(dāng)然排在最前面的堆是每個(gè)進(jìn)程的主heap。)

由此可見(jiàn),對(duì)于crt來(lái)說(shuō),由于每個(gè)dll都有自己的heap,所以每個(gè)dll通過(guò)new/malloc分配的內(nèi)存都是在自己dll內(nèi)部的那個(gè)heap上用HeapAlloc來(lái)分配的,而如果你想在其它模塊中釋放,那么在釋放的時(shí)候HeapFree就會(huì)失敗了,因?yàn)楦鱾€(gè)模塊的__crtheap是不一樣的。

那么如果有非要用到跨模塊釋放的場(chǎng)景呢,可以使用以下幾種方式來(lái)解決:

一、MT改MD

一個(gè)進(jìn)程的地址空間是由一個(gè)可執(zhí)行模塊和多個(gè)DLL模塊構(gòu)成的,這些模塊中,有些可能會(huì)鏈接到C/C++運(yùn)行庫(kù)的靜態(tài)版本,有些可能會(huì)鏈接到C/C++運(yùn)行庫(kù)的DLL版本。當(dāng)使用運(yùn)行庫(kù)的DLL版本時(shí),由于dll加載到進(jìn)程中只會(huì)在地址空間中存有一份,因此共用的是同一個(gè)堆。所以將可執(zhí)行模塊和DLL模塊統(tǒng)一修改為MD編譯,則可以直接實(shí)現(xiàn)跨模塊之間的內(nèi)存申請(qǐng)和釋放,而不會(huì)存在任何問(wèn)題。

更多MT和MD,以及DLL和進(jìn)程地址空間的知識(shí)可以參考文末補(bǔ)充內(nèi)容

二、DLL提供釋放接口

DLL提供統(tǒng)一的對(duì)外接口,供外部模塊(可執(zhí)行模塊或其它DLL模塊)調(diào)用,由該DLL內(nèi)部來(lái)進(jìn)行內(nèi)存的釋放。簡(jiǎn)單實(shí)現(xiàn)如下:

void __stdcall MyFree(void *ptr)
{
    if (ptr)
    {
        free(ptr);
    }
}
void __stdcall MyDelete(void *ptr)
{
    if (ptr)
    {
        delete ptr;
    }
}
void  __stdcall MyDeleteArray(void *ptr)
{
    if (ptr)
    {
        delete[] ptr;
    }
}

三、使用進(jìn)程堆申請(qǐng)內(nèi)存

在一個(gè)進(jìn)程中,可執(zhí)行模塊和DLL模塊都屬于同一個(gè)進(jìn)程地址空間,而每個(gè)進(jìn)程又都有一個(gè)為主進(jìn)程服務(wù)的堆(一般也稱為進(jìn)程的默認(rèn)堆),當(dāng)我們需要跨模塊進(jìn)行內(nèi)存申請(qǐng)和釋放時(shí),可以在進(jìn)程主堆上進(jìn)行申請(qǐng),同樣地,釋放時(shí),也直接在進(jìn)程主堆上進(jìn)行釋放,這樣就可以不用考慮MT導(dǎo)致的跨進(jìn)程釋放的問(wèn)題。API的使用此處不講解,直接附上簡(jiǎn)易代碼:

在DLL中:

void* __stdcall Test(int *len)
{
	void* pData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 100);
	if (pData == NULL)
		return NULL;
	//使用的是HEAP_ZERO_MEMORY,會(huì)自動(dòng)把內(nèi)存塊的內(nèi)容都清零
	//下面這行代碼可以不要的
	memset(pData, 0, 100);

	char pBuf[] = "十點(diǎn)十分十分十分";
	memcpy(pData, pBuf, sizeof(pBuf));
	*len = 100;
	return pData;
}

在可執(zhí)行模塊中:

int main()
{
	HMODULE hLib = LoadLibraryA("Dll1.dll");
	if (nullptr == hLib)
	{
		std::cout << "LoadLibraryA fail, error:" << GetLastError() << std::endl;
		return 0;
	}

	Fun fun = (Fun)GetProcAddress(hLib, "Test");
	if (nullptr == fun)
	{
		std::cout << "GetProcAddress fail, error:" << GetLastError() << std::endl;
		return 0;
	}

	int nLen = 0;
	char *pData = (char*)fun(&nLen);

	std::string strTemp(pData, nLen);

	HeapFree(GetProcessHeap(), 0, pData);

	std::cout << strTemp << std::endl;

	return 0;
}

使用默認(rèn)的進(jìn)程堆來(lái)申請(qǐng)內(nèi)存還需要注意,很多Windows系統(tǒng)函數(shù)都用到了進(jìn)程的默認(rèn)堆,而且應(yīng)用程序會(huì)可能有多個(gè)線程同時(shí)要調(diào)用各種windows函數(shù),因此系統(tǒng)保證不管在什么時(shí)候,一次只讓一個(gè)線程從默認(rèn)堆中分配或者釋放內(nèi)存快。當(dāng)兩個(gè)線程同時(shí)想要從默認(rèn)堆中分配一塊內(nèi)存,那么只有一個(gè)線程能夠分配,另一個(gè)線程必須等待第一個(gè)線程的分配完成。這種依次訪問(wèn)對(duì)性能會(huì)有輕微影響,在一般的應(yīng)用程序中可以忽略不計(jì),對(duì)性能要求較高的程序需要注意。

四、知識(shí)補(bǔ)充

DLL和進(jìn)程的地址空間

DLL是Windows開(kāi)發(fā)人員經(jīng)常使用到的一種技術(shù),比如我們經(jīng)常會(huì)把相同功能的代碼封裝到一個(gè)模塊中,然后供其他需要使用該模塊的程序共同調(diào)用,可以降低代碼的復(fù)用性,使用起來(lái)非常方便;而且,當(dāng)我們需要對(duì)外部提供自己公司的接口時(shí),也會(huì)考慮到使用dll,它可以將我們內(nèi)部實(shí)現(xiàn)的代碼進(jìn)行封裝保護(hù),而不會(huì)暴露給使用者。

下面主要討論DLL和調(diào)用DLL的進(jìn)程的地址空間的關(guān)系,避免使用DLL的過(guò)程中,造成難以預(yù)測(cè)的內(nèi)存泄漏問(wèn)題,以及程序崩潰問(wèn)題。

MT和MD的區(qū)別

大家在使用windows編程時(shí),都會(huì)發(fā)現(xiàn)有一個(gè)運(yùn)行庫(kù)的編譯選項(xiàng),MT和MD(MTD和MDD只是對(duì)應(yīng)的debug調(diào)試模式)。我們以c/c++運(yùn)行庫(kù)為例,如果我們的應(yīng)用程序選擇連接到C/C++運(yùn)行庫(kù)的靜態(tài)版本,那么諸如_tcscpy,malloc之類的函數(shù)會(huì)在內(nèi)存中出現(xiàn)多次;但是如果連接到C/C++運(yùn)行庫(kù)的DLL版本,那么這些函數(shù)就只是在內(nèi)存中出現(xiàn)一次,這意味著內(nèi)存的使用率非常高,而這個(gè)也是windows操作系統(tǒng)從誕生之初就推出DLL的主要原因。

1、使用MD的場(chǎng)景:

(1)程序就不需要靜態(tài)鏈接運(yùn)行時(shí)庫(kù),可以減小軟件的大小;

(2)所有的模塊都采用/MD,使用的是同一個(gè)堆,不存在A堆申請(qǐng),B堆釋放的問(wèn)題;

(3)用戶機(jī)器可能缺少我們編譯時(shí)使用的動(dòng)態(tài)運(yùn)行時(shí)庫(kù)。(補(bǔ)充:如果我們軟件有多個(gè)DLL,采用/MT體積增加太多,則可以考慮/MD + 自帶系統(tǒng)運(yùn)行時(shí)庫(kù))

2、使用MT的場(chǎng)景:

(1)有些系統(tǒng)可能沒(méi)有程序所需要版本的運(yùn)行時(shí)庫(kù),程序必須把運(yùn)行時(shí)庫(kù)靜態(tài)鏈接上。

(2)減少模塊對(duì)外界的依賴。

顯示鏈接與隱式鏈接

在應(yīng)用程序能夠調(diào)用一個(gè)DLL中的函數(shù)之前,必須將該DLL的文件映像映射到調(diào)用進(jìn)程的地址空間中。我們可以通過(guò)兩種方法來(lái)達(dá)到這一目的:隱式載入時(shí)鏈接或顯示運(yùn)行時(shí)鏈接。

1、顯示鏈接

通過(guò)WIN32 API來(lái)實(shí)現(xiàn)的一種鏈接方式。

(1)LoadLibraryA將對(duì)應(yīng)DLL映射到當(dāng)前調(diào)用進(jìn)程的地址空間中

(2)GetProcAddress獲取DLL中的導(dǎo)出函數(shù)地址,通過(guò)函數(shù)地址調(diào)用函數(shù)。

(3)FreeLibrary從調(diào)用進(jìn)程的地址空間中撤銷對(duì)DLL的映射。

2、隱式鏈接

當(dāng)編寫(xiě)DLL工程代碼時(shí),如果鏈接器檢測(cè)到DLL的源文件輸出了至少一個(gè)變量或函數(shù)時(shí)時(shí),那么鏈接器或會(huì)生成一個(gè).lib文件,這個(gè).lib文件非常小,這是因?yàn)樗⒉话魏魏瘮?shù)或者變量,它只是列出了所有被導(dǎo)出的函數(shù)和變量的符號(hào)名。

把DLL隱式鏈接到調(diào)用進(jìn)程時(shí),,我們需要用到三個(gè)文件,DLL相關(guān)的.h文件,.lib文件和.dll文件,前兩個(gè)文件需要通過(guò)配置工程項(xiàng)目屬性,當(dāng)編譯的時(shí)候鏈接器把導(dǎo)出函數(shù)和變量相關(guān)信息加入到調(diào)用進(jìn)程的可執(zhí)行模塊的導(dǎo)入段中,當(dāng)可執(zhí)行模塊運(yùn)行時(shí),根據(jù)導(dǎo)入段的信息去加載對(duì)應(yīng)的DLL。

DLL和進(jìn)程的地址空間

一旦系統(tǒng)將一個(gè)DLL的文件映像映射到調(diào)用進(jìn)程的地址空間后,進(jìn)程中的所有線程都可以調(diào)用該DLL中的函數(shù)了。此時(shí),該DLL中的代碼和數(shù)據(jù)全部存放在進(jìn)程的地址空間中,且DLL中的函數(shù)創(chuàng)建的任何對(duì)象都為調(diào)用線程或調(diào)用進(jìn)程所擁有-DLL絕對(duì)不會(huì)擁有任何對(duì)象。

舉個(gè)例子,如果DLL中的一個(gè)函數(shù)調(diào)用了VirtualAlloc,系統(tǒng)就會(huì)從調(diào)用進(jìn)程的地址空間中預(yù)定地址空間區(qū)域(即申請(qǐng)內(nèi)存)。如果稍后從進(jìn)程的地址空間中撤銷對(duì)DLL的映射,那么這塊地址區(qū)域仍然被保持為預(yù)定狀態(tài)(DLL被從調(diào)用進(jìn)程中取消映射, 并不會(huì)主動(dòng)釋放動(dòng)態(tài)分配的內(nèi)存)。被預(yù)定的空間區(qū)域的擁有者是進(jìn)程,只有當(dāng)線程調(diào)用了VirtualFree函數(shù)或者當(dāng)進(jìn)程終止時(shí),該區(qū)域才會(huì)被釋放。

也就是說(shuō),當(dāng)一個(gè)DLL被加載到調(diào)用進(jìn)程的地址空間內(nèi),DLL內(nèi)所有申請(qǐng)的內(nèi)存空間都是屬于調(diào)用進(jìn)程的,靜態(tài)變量和全局變量都會(huì)是一份全新的實(shí)例。對(duì)于DLL中申請(qǐng)的內(nèi)存要記得釋放掉,并且嚴(yán)格遵循“誰(shuí)申請(qǐng)誰(shuí)釋放”的原則,盡量不要dll中申請(qǐng)的內(nèi)存,然后在調(diào)用線程中直接通過(guò)操作符或者API函數(shù)進(jìn)行刪除(當(dāng)dll和調(diào)用進(jìn)程使用的運(yùn)行庫(kù)編譯選項(xiàng)不是同時(shí)為MD時(shí),會(huì)導(dǎo)致程序崩潰)

寫(xiě)了一個(gè)簡(jiǎn)單例子,來(lái)驗(yàn)證每次DLL映射到進(jìn)程地址空間中時(shí),DLL中的全局變量的實(shí)例都是重新創(chuàng)建的。

DLL代碼很簡(jiǎn)單,就是一個(gè)全局變量

調(diào)用進(jìn)程代碼,就是不停地加載DLL,卸載DLL,加載DLL…

初始運(yùn)行時(shí),程序的內(nèi)存大小如圖所示:

10秒時(shí)間不到,內(nèi)存增長(zhǎng)到30多兆

因此,在編寫(xiě)DLL工程時(shí),對(duì)于new出來(lái)的全局變量,一定要在DLL從調(diào)用進(jìn)程中撤銷映射時(shí),進(jìn)行釋放,避免引起內(nèi)存泄漏。

到此這篇關(guān)于淺析C++如何跨模塊釋放內(nèi)存的文章就介紹到這了,更多相關(guān)C++跨模塊釋放內(nèi)存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C語(yǔ)言實(shí)現(xiàn)宿舍管理課程設(shè)計(jì)

    C語(yǔ)言實(shí)現(xiàn)宿舍管理課程設(shè)計(jì)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)宿舍管理課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • OpenCV + MFC實(shí)現(xiàn)簡(jiǎn)單人臉識(shí)別

    OpenCV + MFC實(shí)現(xiàn)簡(jiǎn)單人臉識(shí)別

    這篇文章主要為大家詳細(xì)介紹了OpenCV + MFC實(shí)現(xiàn)簡(jiǎn)單人臉識(shí)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • C++如何實(shí)現(xiàn)簡(jiǎn)單的計(jì)時(shí)器詳解

    C++如何實(shí)現(xiàn)簡(jiǎn)單的計(jì)時(shí)器詳解

    因?yàn)樽罱e著無(wú)聊就想著要不用C++寫(xiě)點(diǎn)什么東西,仔細(xì)想了想其實(shí)自己的C++學(xué)的也不怎么好,寫(xiě)個(gè)簡(jiǎn)單的計(jì)時(shí)器吧!所以下面這篇文章主要介紹了利用C++如何實(shí)現(xiàn)簡(jiǎn)單的計(jì)時(shí)器,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-01-01
  • C++使用文件實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)

    C++使用文件實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C++使用文件實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-01-01
  • C語(yǔ)言非遞歸算法解決快速排序與歸并排序產(chǎn)生的棧溢出

    C語(yǔ)言非遞歸算法解決快速排序與歸并排序產(chǎn)生的棧溢出

    上期我們講完了排序算法下,不知道小伙伴們有沒(méi)有發(fā)現(xiàn)一個(gè)問(wèn)題,快速排序和歸并排序我們都是用遞歸來(lái)實(shí)現(xiàn)的,可能有小伙伴會(huì)問(wèn),如果說(shuō)數(shù)據(jù)量很多話,棧區(qū)空間會(huì)不會(huì)不夠用呢?這期我們就來(lái)解決使用遞歸實(shí)現(xiàn)的排序?qū)е聴R绯鋈绾谓鉀Q
    2022-04-04
  • 帶你粗略了解C++流的讀寫(xiě)文件

    帶你粗略了解C++流的讀寫(xiě)文件

    這篇文章主要為大家總結(jié)了C++中輸入輸出流及文件流操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能給你帶來(lái)幫助
    2021-08-08
  • Qt?CEF融合技QCefView使用教程(推薦)

    Qt?CEF融合技QCefView使用教程(推薦)

    QCefView是一個(gè)與Chromium?Embedded?Framework集成的Qt第三方開(kāi)源庫(kù),LGPL許可,可以在項(xiàng)目中免費(fèi)使用,功能類似CEF、QWebEngineView,提供C++和web交互的能力,本文給大家介紹Qt?CEF融合技QCefView使用教程,感興趣的朋友參考下吧
    2021-12-12
  • C++插入排序算法實(shí)例

    C++插入排序算法實(shí)例

    這篇文章主要介紹了C++插入排序算法實(shí)例,本文先是講解了什么插入排序,然后給出了C++代碼實(shí)例,需要的朋友可以參考下
    2014-10-10
  • C++中allocator類使用示例

    C++中allocator類使用示例

    大家好,本篇文章主要講的是C++中allocator類使用示例,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下
    2022-02-02
  • C++名稱空間特性

    C++名稱空間特性

    這篇文章主要介紹了C++名稱空間特性,文章圍繞C++名稱空間特性的相關(guān)資料展開(kāi)詳細(xì)內(nèi)容,需要的小伙伴可以參考一下下文具體內(nèi)容,希望對(duì)你的學(xué)習(xí)有所幫助
    2022-01-01

最新評(píng)論