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

淺析C++如何跨模塊釋放內存

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

在開發(fā)主程序和動態(tài)庫時,首要原則就是:避免跨模塊申請和釋放內存。這一點,我們在很多開源庫或者平常項目中也都碰到過,對于動態(tài)庫中的堆內存申請與釋放,動態(tài)庫總是會提供兩個接口分別實現(xiàn)new和delete操作,而不會讓調用方自己去操作。但有時候如果違背了這個原則呢,在linux平臺上不會存在這樣的憂慮,因為在linux下,每個進程只有一個heap,在任何一個動態(tài)庫模塊so中通過new或者malloc來分配內存的時候都是從這個唯一的heap中分配的,那么自然你在其它隨便什么地方釋放都是沒問題的。這個模型是簡單的。而windows下就變得復雜了,下面主要介紹一下windows下的主程序和dll之間跨模塊內存釋放的問題。

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

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

由此可見,對于crt來說,由于每個dll都有自己的heap,所以每個dll通過new/malloc分配的內存都是在自己dll內部的那個heap上用HeapAlloc來分配的,而如果你想在其它模塊中釋放,那么在釋放的時候HeapFree就會失敗了,因為各個模塊的__crtheap是不一樣的。

那么如果有非要用到跨模塊釋放的場景呢,可以使用以下幾種方式來解決:

一、MT改MD

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

更多MT和MD,以及DLL和進程地址空間的知識可以參考文末補充內容

二、DLL提供釋放接口

DLL提供統(tǒng)一的對外接口,供外部模塊(可執(zhí)行模塊或其它DLL模塊)調用,由該DLL內部來進行內存的釋放。簡單實現(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;
    }
}

三、使用進程堆申請內存

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

在DLL中:

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

	char pBuf[] = "十點十分十分十分";
	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;
}

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

四、知識補充

DLL和進程的地址空間

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

下面主要討論DLL和調用DLL的進程的地址空間的關系,避免使用DLL的過程中,造成難以預測的內存泄漏問題,以及程序崩潰問題。

MT和MD的區(qū)別

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

1、使用MD的場景:

(1)程序就不需要靜態(tài)鏈接運行時庫,可以減小軟件的大?。?/p>

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

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

2、使用MT的場景:

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

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

顯示鏈接與隱式鏈接

在應用程序能夠調用一個DLL中的函數(shù)之前,必須將該DLL的文件映像映射到調用進程的地址空間中。我們可以通過兩種方法來達到這一目的:隱式載入時鏈接或顯示運行時鏈接。

1、顯示鏈接

通過WIN32 API來實現(xiàn)的一種鏈接方式。

(1)LoadLibraryA將對應DLL映射到當前調用進程的地址空間中

(2)GetProcAddress獲取DLL中的導出函數(shù)地址,通過函數(shù)地址調用函數(shù)。

(3)FreeLibrary從調用進程的地址空間中撤銷對DLL的映射。

2、隱式鏈接

當編寫DLL工程代碼時,如果鏈接器檢測到DLL的源文件輸出了至少一個變量或函數(shù)時時,那么鏈接器或會生成一個.lib文件,這個.lib文件非常小,這是因為它并不包含任何函數(shù)或者變量,它只是列出了所有被導出的函數(shù)和變量的符號名。

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

DLL和進程的地址空間

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

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

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

寫了一個簡單例子,來驗證每次DLL映射到進程地址空間中時,DLL中的全局變量的實例都是重新創(chuàng)建的。

DLL代碼很簡單,就是一個全局變量

調用進程代碼,就是不停地加載DLL,卸載DLL,加載DLL…

初始運行時,程序的內存大小如圖所示:

10秒時間不到,內存增長到30多兆

因此,在編寫DLL工程時,對于new出來的全局變量,一定要在DLL從調用進程中撤銷映射時,進行釋放,避免引起內存泄漏。

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

相關文章

  • C語言實現(xiàn)宿舍管理課程設計

    C語言實現(xiàn)宿舍管理課程設計

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

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

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

    C++如何實現(xiàn)簡單的計時器詳解

    因為最近閑著無聊就想著要不用C++寫點什么東西,仔細想了想其實自己的C++學的也不怎么好,寫個簡單的計時器吧!所以下面這篇文章主要介紹了利用C++如何實現(xiàn)簡單的計時器,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-01-01
  • C++使用文件實現(xiàn)學生信息管理系統(tǒng)

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

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

    C語言非遞歸算法解決快速排序與歸并排序產生的棧溢出

    上期我們講完了排序算法下,不知道小伙伴們有沒有發(fā)現(xiàn)一個問題,快速排序和歸并排序我們都是用遞歸來實現(xiàn)的,可能有小伙伴會問,如果說數(shù)據(jù)量很多話,棧區(qū)空間會不會不夠用呢?這期我們就來解決使用遞歸實現(xiàn)的排序導致棧溢出如何解決
    2022-04-04
  • 帶你粗略了解C++流的讀寫文件

    帶你粗略了解C++流的讀寫文件

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

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

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

    C++插入排序算法實例

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

    C++中allocator類使用示例

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

    C++名稱空間特性

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

最新評論