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

深入探究C++編程中的資源泄漏問題以及排查方法

 更新時間:2023年10月08日 09:20:35   作者:dvlinker  
在C++程序開發(fā)維護過程中,時常會遇到資源泄漏問題,比如GDI對象泄漏、進程線程句柄泄漏以及內(nèi)存泄漏問題,今天我們就來深入探討一下這幾類資源泄漏以及排查這些泄露的辦法,需要的朋友可以參考下

1、GDI對象泄漏

在Windows平臺上,做UI客戶端編程,很多時候都是使用系統(tǒng)GDI對象進行窗口的繪制,常見的GDI對象有Pen(用來繪制線條的畫筆)、Brush(用來填充顏色的畫刷)、Bitmap(用來處理圖片的位圖)、Font(用來設(shè)置文字大小的字體)、Region(區(qū)域)、DC(設(shè)備上下文)等。

1.1、何為GDI資源泄漏?

對于Pen、Brush、Bitmap和Region等,在使用前我們需要調(diào)用創(chuàng)建這些對象的接口把對象創(chuàng)建出來,比如CreatePen/CreatePenIndirect、CreateSolidBrush/CreateBrushIndirect、CreateFont/CreateFontIndirect、CreateCompatibleBitmap等API接口,然后在使用完這些對象后需要調(diào)用DeleteObject將對象釋放掉。對于DC對象,則一般調(diào)用GetDC去獲取窗口的DC對象,然后在不使用時需要調(diào)用ReleaseDC將DC釋放掉。如果不釋放這些對象,則會導(dǎo)致GDI對象泄漏。

在Windows程序中,一個進程的GDI對象總數(shù)是有上限的,默認情況下上限值為10000個??梢詮娜缦碌淖员碇锌梢钥吹剑@個值是系統(tǒng)設(shè)置的默認值,一般情況下不用修改,即使修改,也不能改成很大的值。

如果發(fā)生GDI對象泄漏的代碼段,頻繁地執(zhí)行,程序在持續(xù)運行一段時間后,進程的GDI對象總數(shù)接近或達到10000個上限。當(dāng)接近上限時,就會出現(xiàn)GDI繪圖函數(shù)內(nèi)部發(fā)生錯誤,返回失敗,導(dǎo)致窗口繪制異常。緊接著可能就會產(chǎn)生崩潰閃退。

1.2、使用GDIView工具排查GDI對象泄漏

GDI對象持續(xù)泄漏,對程序可能是致命的,一旦接近或達到上限,就會導(dǎo)致程序發(fā)聲崩潰閃退。GDI對象泄漏問題,排查起來相對容易一些,先用GDIView工具先看一下是哪類GDI對象有泄漏

然后有針對性的查看操作這類GDI對象的代碼,然后逐步縮小排查的范圍。

如果出現(xiàn)窗口繪制或顯示異常,或者程序無故閃退,可以到任務(wù)管理器中查看進程的GDI對象總數(shù)的值:(默認情況下不顯示GDI對象列,右鍵點擊標(biāo)題欄,在彈出窗口中勾選GDI對象選項即可顯示)

如果總數(shù)接近10000個,肯定是GDI對象泄漏導(dǎo)致的??梢灾匦聠映绦颍缓笤偃蝿?wù)管理器中持續(xù)觀察進程的GDI對象總數(shù)。

1.3、有時可能需要結(jié)合其他方法去排查

有時也要結(jié)合其他方法來輔助定位,比如可以使用歷史版本比對法,看看是從哪天開始出現(xiàn)泄漏。然后查看前一天svn或git上的代碼提交記錄,或者底層模塊庫發(fā)布記錄,這樣就能有效的縮小問題的排查范圍。有次項目中出的問題,就出在底層的WebRTC開源庫中。當(dāng)時排查了UI層的代碼沒有找到泄漏點,所以懷疑可能是底層模塊有問題。

當(dāng)時找到了問題的復(fù)現(xiàn)辦法,然后使用歷史版本比對法,確定了從哪一天開始出現(xiàn)泄漏。然后查看了svn上的代碼提交記錄以及底層庫的發(fā)布記錄, 發(fā)現(xiàn)出問題前一天底層開源組件組發(fā)布了新版本的WebRTC開源庫,在這個版本中開源組件組為了處理一個bug,添加了一段代碼,于是找開源組件的同事排查一下他們提交的代碼,看看是否存在GDI泄漏。一小段時間后,他們給出結(jié)論,說他們新加的代碼沒問題,應(yīng)該是其他模塊引發(fā)的。但根據(jù)歷史版本比對法的對比,問題應(yīng)該就出在WebRTC開源庫中,但開源組件組始終覺得他們的代碼沒問題。

于是我到開源組件組那邊查看svn上他們的代碼修改記錄,看到他們新增的一段代碼果然有問題,如下所示:

#if defined (WEBRTC_WIN)
    //修正程序開啟DWM導(dǎo)致的鼠標(biāo)位置問題
    int desktop_horzers = GetDeviceCaps( GetDC(nullptr) DESKTOPHORZRES); // 問題就出在這個GetDC上
    int horzers = GetDeviceCaps(GetDC(nullptr),HORZRES);
    float scale_rate=(float)desktop_horzers/(float)horzers;
    relative_position.set( relative_ position.x()*scale_rate, 
        relative_ position.y()*scale_rate );
#endif

這段代碼中,他們調(diào)用GetDC接口獲取窗口的DC對象,在使用完DC對象后,沒有調(diào)用ReleaseDC將DC對象釋放掉,所以導(dǎo)致了DC對象的泄漏。修改后的代碼如下:

#if defined (WEBRTC_WIN)? ? //修正程序開啟DWM導(dǎo)致的鼠標(biāo)位置問題? ? HDC hDC = ::GetDC(nullptr);? ? int desktop_horzers = GetDeviceCaps( hDC, DESKTOPHORZRES);? ? int horzers = GetDeviceCaps(hDC,HORZRES);? ? float scale_rate=(float)desktop_horzers/(float)horzers;? ? relative_position.set( relative_ position.x()*scale_rate,?? ? ? ? relative_ position.y()*scale_rate );? ? ::ReleaseDC(nullptr, hDC);#endif

至于開源組件的同事沒找到問題,可能是他們對UI編程不熟悉導(dǎo)致的。

1.4、如何保證沒有GDI對象泄漏?

要保證不出現(xiàn)GDI對象泄漏,在GDI對象使用完成后要將之刪除或釋放掉,如果不刪除或釋放,則會導(dǎo)致GDI泄漏。比如使用CreateXXXXXX創(chuàng)建的GDI對象,使用完后,要用DeleteObject釋放;調(diào)用LoadXXXXXX函數(shù)去加載圖片資源,使用完后,也要用DeleteObject釋放;調(diào)用CreateXXXDC創(chuàng)建的DC對象,使用完后,要用DeleteDC去釋放;調(diào)用GetDC獲取到的DC對象,使用完后,要用ReleaseDC釋放。

調(diào)用不用的接口去創(chuàng)建或獲取GDI對象,釋放時也要調(diào)用對應(yīng)的釋放接口,不能混淆!在這里給大家大概的羅列一下:

創(chuàng)建或獲取GDI對象刪除或釋放GDI對象
CreatePen/CreatePenIndirect(pen畫筆對象)、CreateSolidBrush/CreateBrushIndirect(brush畫刷對象)、CreateFont/CreateFontIndirect(Font字體對象)、CreateCompatibleBitmap(BItmap位圖對象)對于Create出來的對象,要調(diào)用DeleteObject釋放
CreateDC/CreateCompatibleDC(創(chuàng)建DC對象)調(diào)用DeleteDC釋放
GetDC(獲取DC對象)調(diào)用ReleaseDC釋放
LoadBitmap(加載Bitmap位圖)調(diào)用DeleteObject釋放
LoadImage(加載圖片資源)

如果加載的是Bitmap位圖,則調(diào)用DeleteObject釋放;

如果加載的是Cursor光標(biāo),則調(diào)用DestroyCursor釋放;

如果加載的是Icon圖標(biāo),則調(diào)用DestroyIcon釋放。

對于上面提到的創(chuàng)建GDI對象的API函數(shù),在釋放時該調(diào)用哪個接口,直接到MSDN上查看API接口的Remarks部分就會找到對應(yīng)的說明。比如創(chuàng)建兼容位圖的API函數(shù)CreateCompatibleBItmap,在Remaks部分的說明如下:

再比如加載圖片的API函數(shù)LoadImage,其在Remarks部分的說明如下:

在調(diào)用Windows系統(tǒng)API函數(shù)遇到問題時,需要到微軟MSDN幫助頁面中查看API函數(shù)的詳細說明(可能會給出調(diào)用函數(shù)時的注意事項,或者調(diào)用函數(shù)的示例代碼等),在說明中可能會找到相關(guān)的原因!會使用MSDN,是一個Windows開發(fā)人員最基本的要求!

2、進程句柄泄漏

進程句柄包括文件句柄(打開文件時產(chǎn)生的句柄)、注冊表句柄(打開注冊表節(jié)點時產(chǎn)生的句柄)、事件句柄信號量句柄、線程句柄(創(chuàng)建線程時產(chǎn)生的句柄)、進程句柄(創(chuàng)建子進程時產(chǎn)生的句柄)等。

2.1、何為進程句柄泄漏?

這些句柄在使用完成后需要及時釋放,如果不釋放,則會造成句柄泄漏。一般調(diào)用CloseHandle去釋放句柄,比如進程句柄、線程句柄、事件句柄、文件句柄等。當(dāng)然也有部分句柄需要對應(yīng)的接口去釋放,比如注冊表句柄需要調(diào)用RegCloseKey去關(guān)閉。

在Winows系統(tǒng)中,進程句柄數(shù)也是有上限的,默認也是10000個,也有對應(yīng)的注冊表項。當(dāng)進程的句柄數(shù)接近或達到10000個上限時,就會導(dǎo)致后續(xù)產(chǎn)生句柄的操作會執(zhí)行失敗,比如調(diào)用CreateThread去創(chuàng)建新的線程會失敗。關(guān)于進程GDI對象上限值的注冊表設(shè)置路徑為:

計算機\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

不僅進程的GDI對象有上限,進程的句柄數(shù)(比如線程句柄、事件句柄等句柄)也是有上限的,默認也是10000個。注冊表對應(yīng)的節(jié)點配置如下:

這兩個配置項的說明如下:

1)GDIProcessHandleQuota項:設(shè)置GDI句柄數(shù)量,默認值為2710(16進制)/10000(10進制),該值的允許范圍為 256 ~ 16384 ,將其調(diào)整為大于默認的10000的值。如果您的系統(tǒng)配置了2G或更多內(nèi)容,不妨將其設(shè)置為允許的最大值 16384(10進制)。

2)USERProcessHandleQuota項:設(shè)置用戶句柄數(shù)量,默認值同樣為2710(16進制)/10000(10進制),該值的允許范圍為 200 ~ 18000 ,將其調(diào)整為更多的數(shù)值。同樣地,對于具有2GB或更多物理內(nèi)存的系統(tǒng),不妨將用戶句柄數(shù)直接設(shè)置為上限 18000(10進制)。

2.2、創(chuàng)建線程時的線程句柄泄漏 

以前我們在項目中就遇到這樣的問題,有的業(yè)務(wù)子系統(tǒng)是通過https和平臺服務(wù)器交互的,客戶端每執(zhí)行一個https操作時都會創(chuàng)建一個線程去執(zhí)行,但創(chuàng)建線程后沒有調(diào)用CloseHanlde將線程句柄關(guān)閉掉,導(dǎo)致線程句柄發(fā)生泄漏,當(dāng)多次執(zhí)行https操作導(dǎo)致線程句柄過多,導(dǎo)致后續(xù)再去創(chuàng)建線程創(chuàng)建失敗了。可以到Process Explorer中查看進程都占用哪些具體的句柄:

這個地方需要注意一下,調(diào)用CloaseHandle將線程句柄釋放掉:

HANDLE hThread = ::CreateThread( NULL, NULL, ProcessProc, this, NULL, NULL );
if ( hThread != NULL )
{
	CloseHandle( hThread );
}

調(diào)用CloseHandle將句柄釋放掉,并不表示將線程結(jié)束掉,線程是否結(jié)束是要看線程函數(shù)的,線程函數(shù)退出了,則線程就結(jié)束了。

線程結(jié)束了,不會自動關(guān)閉線程句柄。對于線程函數(shù),還有一個細節(jié),發(fā)起線程創(chuàng)建的CreateThread函數(shù)返回了,不代表線程的代碼已經(jīng)執(zhí)行到線程函數(shù)中了。這點我們在項目中遇到過這類的場景。當(dāng)時的問題場景是,線程函數(shù)中訪問了一個指針變量,將該指針變量的值初始化為NULL的操作放在CreateThread函數(shù)調(diào)用之后,如下所示:

// 1、指針變量定義
CVideoDec* g_pVideoDec;
// 2、創(chuàng)建線程
HANDLE hThread = ::CreateThread( NULL, NULL, ProcessProc, this, NULL, NULL );
if ( hThread != NULL )
{
    CloseHandle( hThread );
}
g_pVideoDec = NULL; // 對指針變量進行初始化
// 3、線程函數(shù),函數(shù)中訪問了指針變量g_pVideoDec
DWORD WINAPI ProcessCachedMsgThreadProc( LPVOID lpParameter )
{
      // 線程函數(shù)中訪問到了該指針變量
      g_pVideoDec->StartDec();
      return 1;
}

當(dāng)然這種做法是不規(guī)范的,后來發(fā)現(xiàn)程序會時不時崩潰在線程函數(shù)中,使用Windbg分析下來得知是線程中訪問了未初始化的變量,但這個問題不是必現(xiàn)的。這個不必現(xiàn),就和CreateThread函數(shù)返回后線程是否執(zhí)行到線程函數(shù)中有關(guān)。

有時,CreateThread返回時還沒執(zhí)行到線程函數(shù)中,緊接著就去初始化指針變量的值,是不會崩潰的。但如果CreateThread返回時已經(jīng)執(zhí)行到線程函數(shù)中,就會訪問未初始化的指針變量,Release下未初始化的內(nèi)存是個隨機值,即指針變量的值為隨機值,所以一般都會引發(fā)異常。

3、內(nèi)存泄漏

內(nèi)存泄漏是C++程序使用動態(tài)申請的內(nèi)存時容易出現(xiàn)的一類典型內(nèi)存問題。動態(tài)申請內(nèi)存的方式有多種,比如使用new(要用delete去釋放),比如使用malloc(要用free去釋放),再比如調(diào)用系統(tǒng)API函數(shù)HeapCreate或者HeapAlloc(要用HeapFree去釋放),還有可以調(diào)用API函數(shù)VirtualAlloc(要用VirtualFree去釋放),當(dāng)然還有其他的API函數(shù)。動態(tài)申請的內(nèi)存沒有釋放,則會導(dǎo)致內(nèi)存泄漏。

之所以會導(dǎo)致內(nèi)存泄漏,可能是忘記釋放,也可能是寫了釋放內(nèi)存的代碼,但因為種種原因沒有執(zhí)行到內(nèi)存釋放的代碼,后面這類情況有一定的隱蔽性。下面我們重點說一下后面的這類情況。       

3.1、在多態(tài)中沒有將父類的析構(gòu)函數(shù)聲明為virtual函數(shù),導(dǎo)致沒有執(zhí)行到子類的析構(gòu)函數(shù)

比如如下的多態(tài)代碼:

class CBase
{
public:
    CBase();
    ~CBase();  // 沒有將父類的析構(gòu)函數(shù)設(shè)置為虛函數(shù)     
} 
class CDerived : public class CBase
{
public:
    CDerived();
   ~CDerived();       
} 
// 將new出來的子類對象賦值給父類指針,就是多態(tài)
CBase* pBase = new CDerived;
// ...  // 中間代碼省略
delete pBase;

上述代碼,因為沒有將父類的析構(gòu)函數(shù)~CBase設(shè)置為虛函數(shù),導(dǎo)致執(zhí)行到delete pBase;時沒有調(diào)用子類的析構(gòu)函數(shù),導(dǎo)致子類的部分內(nèi)存沒有釋放,從而引發(fā)內(nèi)存泄漏。特別是新人比較容易犯這類錯誤,之前在幫新人排查問題時遇到過,這個場景下的內(nèi)存泄漏具有一定的隱蔽性。

如果不是析構(gòu)函數(shù),是其他的成員函數(shù),如果父類的接口沒有聲明為virtual,多態(tài)就不會生效,會導(dǎo)致子類重寫的成員函數(shù)不會被執(zhí)行到,子類重寫的成員函數(shù)中可能包含了重要的業(yè)務(wù)代碼,這樣就會導(dǎo)致重要的業(yè)務(wù)代碼沒有執(zhí)行到,導(dǎo)致業(yè)務(wù)出現(xiàn)異常。

之前我們這邊的新人將代碼移植到國產(chǎn)化機器上時就遇到過,新人忘記在父類的接口前添加virtual聲明,導(dǎo)致子類的接扣執(zhí)行不到,導(dǎo)致業(yè)務(wù)出現(xiàn)異常,當(dāng)時他查了很久沒找出問題,后來找我去排查,找到這個原因,所以對這個問題印象很深!

所以,我們在定義C++類時,如果該類可能會被繼承,一般都要將父類的析構(gòu)函數(shù)設(shè)置為虛函數(shù),防止出現(xiàn)上述多態(tài)場景下子類的析構(gòu)函數(shù)執(zhí)行不到導(dǎo)致內(nèi)存泄漏的問題。當(dāng)然,設(shè)置虛函數(shù)有一定的副作用,如果一個類中包含虛函數(shù),則類中會自動添加一個虛函數(shù)表指針,此外虛函數(shù)調(diào)用時也涉及到二次尋址問題(效率上略有影響)。

3.2、使用智能指針shared_ptr發(fā)生循環(huán)引用問題,導(dǎo)致內(nèi)存泄漏

使用shared_ptr可能會出現(xiàn)循環(huán)引用問題,這使用shared_ptr智能指針的一個典型問題(也是一個關(guān)于shared_ptr智能指針的面試題),場景是兩個類中都包含了指向?qū)Ψ降膕hared_ptr對象,這樣會導(dǎo)致new出來的兩個類沒有走析構(gòu),引發(fā)內(nèi)存泄漏問題。

循環(huán)引用問題的示意圖如下:

相關(guān)代碼如下:

#include <iostream>
#include<memory>
using namespace std;
class B;
class A{
    public:
    shared_ptr<B> bptr;
    ~A(){cout<<"~A()"<<endl;}
}
class B
{
    public:
    shared_ptr<A> aptr;
    ~B( ){cout<<"~B()"<<endl;}
}
int main() {
    shared_ptr<A> pa(new A()); // 引用加1
    shared_ptr<B> pb(new B()); // 引用加1
    pa->bptr = pb; // 引用加1
    pa->aptr = pa; // 引用加1
    return 0;
}

執(zhí)行到上述return 0這句代碼時,指向A和B兩個對象的引用計數(shù)都是2。當(dāng)退出main函數(shù)時,先析構(gòu)shared_ptr<B> pb對象,B對象的引用計數(shù)減1,B對象的引用計數(shù)還為1,所以不會delete B對象,不會進入B對象析構(gòu)函數(shù),所以B類中的shared_ptr<A> aptr成員不會析構(gòu),所以此時A對象的引用計數(shù)還是2。當(dāng)析構(gòu)shared_ptr<A> pa時,A的引用計數(shù)減1,A對象的引用計數(shù)變?yōu)?,所以不會析構(gòu)A對象。所以上述代碼會導(dǎo)致A和B兩個new出的對象都沒釋放,導(dǎo)致內(nèi)存泄漏。

為了解決上述問題,引入了weak_ptr,可以將類中包含的shared_ptr成員換成weak_ptr,如下:

相關(guān)代碼如下:

#include <iostream>
#include<nemory>
using namespace std;
class B;
class A{
    public:
    weak_ptr<B> bptr;  // 使用weak_ptr替代shared_ptr
    ~A(){cout<<"~A()"<<endl;}
}
class B
{
    public:
    weak_ptr<A> aptr; // 使用weak_ptr替代shared_ptr
    ~B( ){cout<<"~B()"<<endl;}
}
int main() {
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    pa->bptr = pb;
    pa->aptr = pa;
    return 0;
}

3.3、第三方注入庫有內(nèi)存泄漏,導(dǎo)致進程有內(nèi)存泄漏

第三庫注入到我們程序進程中有兩個典型的場景,一種是輸入法模塊的注入,一種是第三方安全軟件的注入。輸入法要支持所有進程的文字輸入,正式通過遠程注入到所有進程的模塊去感知用戶的輸入的。第三方安全軟件,為了監(jiān)控軟件的數(shù)據(jù)操作,一般也是需要遠程注入到進程中的。

之前項目中就遇到過第三方安全軟件的注入模塊有內(nèi)存泄漏,導(dǎo)致進程內(nèi)存耗盡,引發(fā)程序閃退。對于這類問題,可能其他軟件運行不會觸發(fā)內(nèi)存泄漏,只有我們的軟件才會觸發(fā)內(nèi)存泄漏,這個需要拿出足夠的證據(jù)證明是第三方安全軟件的注入模塊引起的內(nèi)存泄漏,否則客戶會認為這是我們軟件的問題,因為其他軟件都沒問題,客戶可能會不承認這與第三方安全軟件有關(guān)。當(dāng)時的問題原因是,第三方安全軟件處理UDP數(shù)據(jù)監(jiān)控的代碼有內(nèi)存泄漏,因為我們的軟件有大量的音視頻數(shù)據(jù)收發(fā),走的是UDP,所以觸發(fā)了第三方安全軟件注入模塊的內(nèi)存泄漏。在給出足夠的證據(jù)后,客戶找到第三方安全軟件提供商,然后安全廠商才修復(fù)了這個bug。

3.4、內(nèi)存泄漏的危害

如果發(fā)生內(nèi)存泄漏的代碼,不會頻繁地的執(zhí)行,只是偶爾的執(zhí)行一下,不會引起太大的問題。但如果有內(nèi)存泄漏的代碼,被頻繁地執(zhí)行,則會頻繁地泄漏(內(nèi)存不釋放),最終可能會導(dǎo)致進程的內(nèi)存耗盡,引發(fā)Out of memory(內(nèi)存耗盡)的崩潰。

進程啟動時,系統(tǒng)會給進程分配指定大小的虛擬內(nèi)存。以32位程序為例,系統(tǒng)會分配4GB的虛擬內(nèi)存,其中用戶態(tài)虛擬內(nèi)存2GB,內(nèi)核態(tài)虛擬內(nèi)存2GB,一般內(nèi)存泄漏的代碼都在用戶態(tài),所以內(nèi)存持續(xù)泄漏會導(dǎo)致用戶態(tài)虛擬內(nèi)存被用盡,引發(fā)Out of memory的崩潰。當(dāng)然,對于64位程序,會分配足夠大的虛擬內(nèi)存。但用戶的電腦可能很多天不關(guān)機,軟件一直在持續(xù)的運行,如果有持續(xù)的內(nèi)存泄漏,總有內(nèi)存用盡的那一天。

我們可以通過Windows自帶的任務(wù)管理器:

去持續(xù)觀察目標(biāo)進程的內(nèi)存變化情況,如果內(nèi)存持續(xù)增長不回落,則可能存在內(nèi)存泄漏。

此外,Windows自帶的任務(wù)管理器看不到進程的總的虛擬內(nèi)存占用,可以使用Process Explorer工具查看進程占用的總虛擬內(nèi)存,該工具顯示的是用戶態(tài)的虛擬內(nèi)存占用:

我們一般只需要關(guān)注用戶態(tài)的虛擬內(nèi)存,因為業(yè)務(wù)代碼占用的是用戶態(tài)的虛擬內(nèi)存。 

我們的程序是32位的,系統(tǒng)給進程分配了4GB的虛擬內(nèi)存,其中用戶態(tài)虛擬內(nèi)存占2GB,內(nèi)核態(tài)虛擬內(nèi)存占2GB,從上圖中看,當(dāng)前程序進程的用戶態(tài)虛擬內(nèi)存占用達到1.7GB,已經(jīng)快接近2GB的上限了,可能再運行一會,2GB用戶態(tài)的內(nèi)存就要耗盡了,程序就會閃退!

注意,Process Explorer工具默認是不顯示Virtual Size虛擬內(nèi)存列,需要右鍵點擊進程列表的標(biāo)題欄,點擊“Select Columns”,在彈出的窗口中點擊“Process Memory”標(biāo)簽頁,然后將“Virtual Size”選項:

3.5、內(nèi)存泄漏的排查

內(nèi)存泄漏問題的排查,相對比較麻煩,但可以使用一些工具去分析。

3.5.1、Windows平臺上內(nèi)存泄漏的排查

在Windows平臺上,可以使用Windbg(使用!heap命令)、umdh.exe(該工具位于Windbg的安裝目錄中)、DebugDiag、VMMAP以及Visual C++專用的Visual Leak Detector等工具。對于Visual Leak Detector工具,需要將相關(guān)的庫編譯到模塊中。其他幾個工具,則可以直接使用。

此外,從Visual Studio 2019的16.9版本開始,Visual Studio引入了google的強大內(nèi)存監(jiān)測工具AddressSanitizer(AddressSanitizer原先只在Linux系統(tǒng)中被支持,繼承在gcc中),就像gcc那樣提供編譯選項上的支持:

在安裝高版本的Visual Studio時,可以將“C++ AddressSanitizer”安裝選項勾選上,這樣Visual Studio中就支持AddressSanitizer了。

AddressSanitizer(簡稱ASan)是google提供的一款面向C/C++語言的內(nèi)存錯誤問題檢查工具,它可以檢測出堆溢出(Heap buffer overflow)、棧溢出(Stack buffer overflow)、全局變量越界(Global buffer overflow)、已釋放內(nèi)存使用(Use after free )、初始化順序(Initialization order bugs)、內(nèi)存泄漏(Use after free )等多個內(nèi)存問題。

AddressSanitizer項目地址:https://github.com/google/sanitizers/wiki/AddressSanitizer

參考文檔頁面:AddressSanitizerAlgorithm · google/sanitizers Wiki · GitHub

如果要使用AddressSanitizer內(nèi)存檢測工具,必須要使用Visual Studio 2019的16.9及以上的版本。此外,AddressSanitizer不能像Windbg那樣獨立運行,直接附加到目標(biāo)進程上去分析,需要使用AddressSanitizer相關(guān)編譯選項重新編譯代碼才行。 

3.5.2、Linux平臺上內(nèi)存泄漏的排查 

在Linux平臺上,常用的內(nèi)存檢測工具有Valgrind和AddressSanitizer,這兩個工具各有優(yōu)勢。

Valgrind工具可以直接監(jiān)測目標(biāo)進程,不需要重新編譯代碼,用起來比較方便。但Valgrind在監(jiān)測內(nèi)存時比較消耗內(nèi)存,同時會嚴重拖慢程序的運行速度,這對于需要實時響應(yīng)的服務(wù)器來講,是個很大的問題。

AddressSanitizer是google出品的內(nèi)存檢測工具,gcc4.8及以上版本才內(nèi)置了AddressSanitizer,通過編譯選項去使用該工具(需要重新編譯代碼),該工具會占用更少的內(nèi)存,不會明顯拖慢程序的運行速度。不過要使用該工具,需要將gcc4.8及以上的版本才行。

4、最后

上面詳細講解了GDI對象泄漏、進程句柄資源泄漏和內(nèi)存泄漏三大類問題,希望能給大家提供一定的借鑒和參考。

以上就是深入探究C++編程中的資源泄漏問題以及排查方法的詳細內(nèi)容,更多關(guān)于C++編程資源泄漏的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C 語言指針概念的詳解

    C 語言指針概念的詳解

    這里主要介紹C 語言指針,這里整理了詳細的資料,對指針做了詳細說明及簡單示例代碼幫助大家理解什么是指針,有興趣的小伙伴可以參考下
    2016-08-08
  • Pipes實現(xiàn)LeetCode(195.第十行)

    Pipes實現(xiàn)LeetCode(195.第十行)

    這篇文章主要介紹了Pipes實現(xiàn)LeetCode(195.第十行),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • C++中的for-each循環(huán)使用

    C++中的for-each循環(huán)使用

    范圍循環(huán)是C++11引入的特性,用于簡化數(shù)組和容器的遍歷過程,它通過直接操作元素而不是使用索引或迭代器,范圍循環(huán)可以使用引用或const修飾符來控制元素的修改權(quán)限,適用于所有支持begin()和end()方法的容器,該循環(huán)方式不適用于未提供這些方法的C++98/03容器
    2024-09-09
  • C語言實現(xiàn)解析csv格式文件的示例代碼

    C語言實現(xiàn)解析csv格式文件的示例代碼

    CSV,有時也稱為字符分隔值,其文件以純文本形式存儲表格數(shù)據(jù)(數(shù)字和文本),本文為大家整理了C語言解析csv文件的方法,需要的可以參考一下
    2023-06-06
  • opencv 做人臉識別 opencv 人臉匹配分析

    opencv 做人臉識別 opencv 人臉匹配分析

    opencv 人臉識別通過級聯(lián)分類器對特征的分級篩選來確定是否是人臉,每個節(jié)點的正確識別率很高,但正確拒絕率很低,任一節(jié)點判斷沒有人臉特征則結(jié)束運算,宣布不是人臉
    2012-11-11
  • C++?sqlite3數(shù)據(jù)庫配置使用教程

    C++?sqlite3數(shù)據(jù)庫配置使用教程

    SQLite 是一種嵌入式的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),它是一個開源項目,已經(jīng)被廣泛應(yīng)用于各種應(yīng)用程序和操作系統(tǒng)中,這篇文章主要介紹了C++?sqlite3數(shù)據(jù)庫配置使用,需要的朋友可以參考下
    2023-08-08
  • C++中std::string::npos的用法

    C++中std::string::npos的用法

    這篇文章主要介紹了C++中std::string::npos的用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • C語言中static與sizeof查缺補漏篇

    C語言中static與sizeof查缺補漏篇

    static在修飾變量的時候,如果是修飾全局變量,則跟全局變量功能一樣;如果是修改局部變量,則每次調(diào)用的時候,保持著上一次的值;而sizeof是用來判斷一個變量及數(shù)據(jù)類型所占字節(jié)數(shù)的,下面我們詳細來看看
    2022-07-07
  • C++操作SQLite簡明教程

    C++操作SQLite簡明教程

    這篇文章主要介紹了C++操作SQLite簡明教程,包含創(chuàng)建表、插入數(shù)據(jù)、查詢數(shù)據(jù)等常用操作,需要的朋友可以參考下
    2014-06-06
  • C語言實現(xiàn)基于控制臺的電子時鐘

    C語言實現(xiàn)基于控制臺的電子時鐘

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)基于控制臺的電子時鐘,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05

最新評論