1. 程式人生 > >關於vs開發windows程式過程中記憶體檢查二三事

關於vs開發windows程式過程中記憶體檢查二三事

做為一個C/C++程式設計師,面對資源管理是必不可少的。今天,我對我這些年的經驗的一些總結。

每一個程式在執行時都佔用一塊可用的記憶體空間,用於存放動態分配的物件,此記憶體空間稱為程式的自由儲存區或堆。
C 語言程式使用一對標準庫函式 malloc 和 free 在自由儲存區中分配儲存空間,而 C++ 語言則使用 new 和 delete 表示式實現相同的功能。

記得剛開始工作時,面試時就會被問記憶體管理時有哪些需要注意的?那時就會說需要“成對”使用 malloc 和 free 或者 new 和 delete ( new[] 和 delete[] )、過載 new 運算子以及使用 Smart-Ptr ,但是當再問還有沒有時就會…

當然,在開發過程,除非有人嚴格的 review 你的程式碼,否則,任何人都有可能會有失誤而犯申請的資源未被釋放的錯誤。在經歷幾次 review 後發現,人工去跟進資源問題,是一件多麼痛苦的事情。


引入第一個概念:靜態程式碼檢查工具

所謂靜態程式碼檢查就是使用一個工具檢查我們寫的程式碼是否安全和健壯,是否有隱藏的問題。

比如無意間寫了這樣的程式碼:
int n = 10;
char* buffer = new char[n];
buffer[n] = 0;

阿里雲2018雙11雲服務只需99.5元
1核2G記憶體,¥99.5/年
2核4G記憶體,¥545.00/1年
2核4G記憶體,¥927.00/2年
2核4G記憶體,¥1227.00/3年
2核8G記憶體,¥2070.00/3年
直達入口:

http://t.cn/EZ14u8r

這完全是符合語法規範的,但是靜態程式碼檢查工具會提示此處會溢位。也就是說,它是一個更加嚴格的編譯器。

使用比較廣泛的靜態程式碼檢查工具有 CppCheckPC-Lint 等。

PC-Lint 是資格最老,最強力的程式碼檢查工具,但是是收費軟體,並且配置起來有一點點麻煩。CppCheck 是免費的開源軟體。使用起來也很方便。詳細使用傳送門

最開始使用 CppCheck 時,各種跟文件、拼命令列,那叫一個痛苦。糾結了一段時間後,意外的發現了 Riverblade 提供的 Visual Lint,大大增加了我對把控程式碼質量的信心。

Visual Lint 基本上是 C/C++ 開發者編寫高質量程式的必備工具,這個外掛可以很好的實現 CppCheck、PC-Lint 等和 VisualStudio 的整合,使得用起來更方便了。


引入第二個概念:CRT除錯

C/C++ 程式語言的最強大功能之一便是其動態分配和釋放記憶體,但是中國有句古話:“最大的長處也可能成為最大的弱點”,那麼 C/C++ 應用程式正好印證了這句話。在 C/C++ 應用程式開發過程中,動態分配的記憶體處理不當是最常見的問題。其中,最難捉摸也最難檢測的錯誤之一就是記憶體洩漏,即未能正確釋放以前分配的記憶體的錯誤。偶爾發生的少量記憶體洩漏可能不會引起我們的注意,但洩漏大量記憶體的程式或洩漏日益增多的程式可能會表現出各種 各樣的徵兆:從效能不良(並且逐漸降低)到記憶體完全耗盡。更糟的是,洩漏的程式可能會用掉太多記憶體,導致另外一個程式垮掉,而使使用者無從查詢問題的真正根源。此外,即使無害的記憶體洩漏也可能殃及池魚。

幸運的是,Visual Studio 偵錯程式和 C 執行時 (CRT) 庫為我們提供了檢測和識別記憶體洩漏的有效方法。下面請和我一起分享收穫——如何使用 CRT 除錯功能來檢測記憶體洩漏? (傳送門)

有過 MFC 開發經驗的人都知道, MFC 程式如果檢測到存在記憶體洩漏,退出程式的時候會在除錯視窗提醒記憶體洩漏:

Detected memory leaks!
Dumping objects ->
e:\xxxxxxxxx\xxx_impl.cpp(102) : {4722} normal block at 0x00B87E10, 8 bytes long.
Data: < > 18 02 00 00 00 00 00 00

那麼,如果我們不喜歡 MFC ,那麼難道就沒有辦法?或者自己做?

其實,MFC也沒有自己做。記憶體洩漏檢測的工作是VC++的C執行庫做的。也就是說,只要你是VC++程式設計師,都可以很方便地檢測記憶體洩漏。

#include <crtdbg.h>

inline void EnableMemLeakCheck()
{
   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
}

void main()
{
   EnableMemLeakCheck();
   int* leak = new int[10];
}

除錯後,你會發現,在 Debug 輸出視窗也出現了提示:

Detected memory leaks!
Dumping objects ->
{52} normal block at 0x003C4410, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

很好,通過這樣的一個設定,我們能夠很直觀的看出我編寫的程式碼是否有記憶體洩漏了,但是,你馬上會有疑問,我知道我的程式有問題, MFC 程式除錯時能直接幫我們定位程式碼行,但是非 MFC 程式要如何定位呢?
不用著急,前面我們提到過過載 new 運算子這種方式,就是此處的關鍵。在 MFC 程式中,我們很容易發現如下程式碼:

#if defined( _DEBUG ) && ! defined( WFC_STL )
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

中間有一條明確的程式碼項 #define new DEBUG_NEW ,跟蹤除錯,你會發現:

void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)

這也正解釋了 MFC 程式為何能直接定位到程式碼行了。我們也可以在 stdafx.h 中增加如下程式碼:

#ifdef _DEBUG
#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

很好,現在我們也能及時的檢視到哪段程式碼有問題了。


_CrtDumpMemoryLeaks 和 _CrtSetBreakAlloc

  • _CrtDumpMemoryLeaks

確定自程式開始執行以來是否發生過記憶體洩漏,如果發生過,則轉儲所有已分配物件。如果已使用 _CrtSetDumpClient 安裝了掛鉤函式,那麼,_CrtDumpMemoryLeaks每次轉儲 _CLIENT_BLOCK 塊時,都會呼叫應用程式所提供的掛鉤函式。

_CrtDumpMemoryLeaks() 就是顯示當前的記憶體洩漏。 注意是“當前”,也就是說當它執行時,所有未銷燬的物件均會報記憶體洩漏。因此儘量讓這條語句在程式的最後執行。它所反映的是檢測到洩漏的地方。
一般用在 MFC 中比較準確,在 InitInstance 裡面呼叫 _CrtDumpMemoryLeaks

  • _CrtSetBreakAlloc

知道某個錯誤分配塊的分配請求編號後,可以將該編號傳遞給 _CrtSetBreakAlloc 以建立一個斷點
_CrtSetBreakAlloc(51); (或者直接給 _crtBreakAlloc 賦值, _crtBreakAlloc = 51; ) 這樣可以快速在{51}次記憶體洩漏處設上斷點。


另外,Visual Studio 還有專門的外掛叫做 Visual Leak Detector (VLD),未使用過,但我瞭解應該是需要在程式碼中引入相關檔案才能達到效果。
Intel Developer Zone 也有提供一個名為 VTune 的工具,功能非常強大,如果對非要對自己的程式極度死磕的話可以一試,使用傳送門


附一、因為 std::string 是堆分配管理的,做為全域性變數,它會在程式退出後再開始清理,這樣為 std::string 分配的記憶體會被誤報為記憶體洩漏,雖說這是一種可控制的誤報,但是不方便於其它記憶體問題的查詢。


歡迎補充!