1. 程式人生 > >Windows中使用CRT函式檢查記憶體洩露和溢位

Windows中使用CRT函式檢查記憶體洩露和溢位

C++中可以使用new或malloc等函式分配記憶體,通常與delete和free配合使用,但是如果不小心遺忘而程式在持續new或malloc時就會造成程式所佔用的記憶體越來越大,即為“記憶體洩露”。通常寫資料的時候必須在程式開闢的空間中寫,如果不小心寫到了不是程式請求分配的地址中,就可能覆蓋別的有效資料導致程式工作不正常,最常見的就是分配一個數組結果寫的時候傳入的下標過大導致寫超了,這就是“記憶體溢位”。

1.原理

有沒有辦法,可以讓程式幫我們檢測洩露和溢位呢?微軟提供的CRT DBG庫就是為了幹這個的,先說說原理

a.記憶體洩露檢查

既然說了new/delete,malloc/free必須配對使用,那麼在分配記憶體的時候,我們使用連結串列記錄分配的地址,當地址被釋放時溢位對應的分配,最後來檢測連結串列中是否存在未釋放的分配即可判斷是否有記憶體洩露,這一操作通常在程式的結束時檢查。

b.記憶體溢位檢查

記憶體溢位分為“上溢”和“下溢”,為了檢查是否溢位,在分配的資料一頭一尾分別插入對應的標誌資料,微軟使用的是0xfd 0xfd 0xfd 0xfd,檢查是否溢位直接檢查這兩個標誌資料是否被修改即可判斷,當然如果你的程式剛好溢位且寫的資料是0xfd,那沒辦法了,只能靠你自己檢查了。

使用char* a = char[10];分配10位元組

如下如是微軟分配記憶體後的記憶體分配圖,可以看到資料兩端都填充了0xfd,初始化分配的資料填充為0xcd


微軟採用的策略我們也可以從原始碼上來看一看,安裝vs後,微軟提供的有crt原始碼,我的機器上目錄是C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src。

找到crt目錄開啟dbgint.h可以看到分配的dbg block資料結構如下:

#define nNoMansLandSize 4

typedef struct _CrtMemBlockHeader
{
        struct _CrtMemBlockHeader * pBlockHeaderNext;
        struct _CrtMemBlockHeader * pBlockHeaderPrev;
        char *                      szFileName;
        int                         nLine;
#ifdef _WIN64
        /* These items are reversed on Win64 to eliminate gaps in the struct
         * and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
         * maintained in the debug heap.
         */
        int                         nBlockUse;
        size_t                      nDataSize;
#else  /* _WIN64 */
        size_t                      nDataSize;
        int                         nBlockUse;
#endif  /* _WIN64 */
        long                        lRequest;
        unsigned char               gap[nNoMansLandSize];
        /* followed by:
         *  unsigned char           data[nDataSize];
         *  unsigned char           anotherGap[nNoMansLandSize];
         */
} _CrtMemBlockHeader;
可以看到分配的dbg block為雙向連結串列結構,

pBlockHeaderNext和pBlockHeaderPrev分別指向下一個和前一個block

szFileName為分配記憶體的原始碼檔案路徑

nLine為分配記憶體的原始碼行數

nDataSize為分配的block大小

nBlockUse為分配的block標誌號,這個等下會說到

gap[nNoMansLandSize]為實際資料的前置標誌位

可以看到最後的註釋說明,

這個連結串列頭後面跟的就是實際資料data[nDataSize]

實際資料後跟的是實際資料的後置標誌位

nNoMansLandSize定義為4剛好是4個0xfd的大小

c.堆型別

微軟crtdbg定義了五種block堆型別,這裡列舉如下,後面會說到

_NORMAL_BLOCK malloc或alloc分配的堆型別。平常,我們的Debug版本,分配的都是Normal Block。

_CRT_BLOCK 這個是CRT自身分配的記憶體堆,我們不應該指定這種型別,除非有特殊需求。

_CLIENT_BLOCK 可記錄更多的除錯資訊,dbg版new分配的堆型別 

_FREE_BLOCK 讓被釋放的記憶體不從維護的連結串列中移除,僅僅是標記為釋放,並且使用0xDD清除裡面的資料

_IGNORE_BLOCK 不跟蹤記錄堆的資訊,說白了,就算是洩露和溢位也不會報告

2.crt庫使用

在使用微軟提供的crt檢測方案的時候,我們只需要包含crtdbg.h即可。

1.記憶體洩露檢查

呼叫_CrtDumpMemoryLeaks,可輸出到呼叫為止,分配但沒有用釋放的記憶體。

比如如下程式

#include <stdio.h>
#include <crtdbg.h>

int main()
{
	int* a= new int[50];
	_CrtDumpMemoryLeaks();
}
F5執行後,在輸出中檢視如下:


指明瞭洩露的記憶體位置0x006F4B40和大小200Byte,這裡的67即為分配的block標誌號

但是我們想直接定位到程式中的位置,這樣還是太不直觀,用兩種方法:

a.

最開始呼叫_CrtSetBreakAlloc(67);

再F5一遍程式,則程式就會在分配的地方斷點,這時候檢視呼叫堆疊即可判斷洩漏位置


b.

我們在上面介紹微軟呢原始碼的時候說了,dbg版分配的記憶體是記錄了檔案和位置的,預設沒有開啟輸出,這裡我們開啟輸出即可

malloc函式

在包含crtdbg前,定義_CRTDBG_MAP_ALLOC巨集,檢視crtdbg.h即可看到此時,

#define   malloc(s)             _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
以前呼叫malloc的地方現在使用_malloc_dbg,傳入了當前的檔案路徑和行號,分配的堆型別為_NORMAL_BLOCK.

需要注意在MFC、ATL、WTL等庫中預設包含了crtdbg.h,由於我們顯示定位行號需要開啟_CRTDBG_MAP_ALLOC巨集後再連結,需要將在這些庫檔案包含之前定義巨集。

new

對於new開啟巨集是沒有用的,我們看ctrdbg.h中的原始碼,對於new的過載定義如下

_Ret_bytecap_(_Size) void * __CRTDECL operator new(
        size_t _Size,
        int,
        const char *,
        int
        );
第一個引數為分配記憶體位元組大小,第二個為堆型別,第三個為檔案路徑,第四個為檔案行號

所以我們在包含crtdbg後,定義new過載如下

#define MY_DEBUG_NEW new(_CLIENT_BLOCK, __FILE__, __LINE__) 
#define new MY_DEBUG_NEW
這樣處理後,程式如下:
#include <stdio.h>

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

#define MY_DEBUG_NEW new(_CLIENT_BLOCK, __FILE__, __LINE__) 
#define new MY_DEBUG_NEW

int main()
{
	//_CrtSetBreakAlloc(67);

	int* a= new int[50];
	_CrtDumpMemoryLeaks();
}

我們再F5執行上面的程式

現在就非常清楚的定位了洩漏位置。

2.記憶體溢位檢查

記憶體溢位檢查非常簡單,只要包含了crtdbg庫,呼叫_CrtCheckMemory即可檢查目前為止的記憶體溢位情況

#include <stdio.h>

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

#define MY_DEBUG_NEW new(_CLIENT_BLOCK, __FILE__, __LINE__) 
#define new MY_DEBUG_NEW

int main()
{
	int* a= new int[50];
	a[50] = 1;
	int* b = (int*)malloc(10);
	_CrtCheckMemory();
}

F5執行程式


可以看到報告了記憶體溢位的堆,和堆分配的檔案和行號。

3.結果報告方式

但是我們在想,這樣每次還得去找當前溢位的Output輸出,能不能程式更加明顯的報告一下呢。

更改上面程式如下:

	_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_WNDW | _CRTDBG_MODE_DEBUG);
	_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_WNDW | _CRTDBG_MODE_DEBUG);

	int* a= new int[50];
	a[50] = 1;
	int* b = (int*)malloc(10);
	_CrtCheckMemory();
_CrtSetReportMode設定了錯誤和警告的報告方式為輸出到除錯Output視窗和彈出警告視窗,這時候執行會彈出如下視窗:


如果是檢查洩露會彈出如下框


_CrtSetReportMode還可以設定輸出到檔案作為日誌系統使用。

4.自動檢查

上面說的都是顯式呼叫_CrtDumpMemoryLeaks和_CrtCheckMemory檢查洩露,

在程式開始設定如下設定,這樣程式在退出的時候會自動檢查記憶體洩露。

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF)

在程式的開始如下設定,這樣程式在每次分配的新的記憶體和退出的時候都會檢查記憶體溢位

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF);
如果覺得每次都檢查溢位會比較消耗效能的話,對於malloc, realloc, free, and _msize. 函式可以如下設定檢查頻率為
int tmp;
tmp = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);              //獲得當前Falg
tmp = (tmp & 0x0000FFFF) | _CRTDBG_CHECK_EVERY_16_DF;   //低16位標誌溢位檢查頻率
_CrtSetDbgFlag(tmp);
可分別設定

_CRTDBG_CHECK_EVERY_16_DF ,_CRTDBG_CHECK_EVERY_128_DF ,_CRTDBG_CHECK_EVERY_1024_DF ,_CRTDBG_CHECK_EVERY_DEFAULT_DF 

分別對應每分配16次檢查一次,每分配128次檢查一次,每分配1024次檢查一次,不檢查

如果設定了_CRTDBG_CHECK_ALWAYS_DF那麼這些頻率設定無效。

3.注意事項

1.所有的這些檢查都必須在Debug模式下使用,想想就明白了,Debug分配的堆帶了很多除錯資訊,Release肯定不能這樣幹。

2.如果上面的輸出不夠用的話,這裡微軟提供了malloc和new的Hook方法,

_CrtSetReportHook 用於自定義當前的Dbg輸出

_CrtSetAllocHook 用於掛住當前的Alloc/ReAlloc/Free,做更多的事

3.Windows API的堆分配Heap...等函式是沒辦法檢查的。為了做這些函式洩露檢查,只有一個辦法——API HOOK。商業庫都這樣麼幹。

針對crtdbg庫的使用,我封裝了一個庫,支援日誌輸出、自定義輸出等功能,最新下載連結

相關推薦

Windows使用CRT函式檢查記憶體洩露溢位

C++中可以使用new或malloc等函式分配記憶體,通常與delete和free配合使用,但是如果不小心遺忘而程式在持續new或malloc時就會造成程式所佔用的記憶體越來越大,即為“記憶體洩露”。通常寫資料的時候必須在程式開闢的空間中寫,如果不小心寫到了不是程式請求分配

Android使用Handler造成記憶體洩露的分析解決

Java使用有向圖機制,通過GC自動檢查記憶體中的物件(什麼時候檢查由虛擬機器決定),如果GC發現一個或一組物件為不可到達狀態,則將該物件從記憶體中回收。也就是說,一個物件不被任何引用所指向,則該物件會在被GC發現的時候被回收;另外,如果一組物件中只包含互相的引用,而沒有來自它們外部的引用(例如有兩個物件A和

Qt記憶體洩露半自動記憶體管理

Qt中幫程式設計師做了一些記憶體回收的事情,但正因為這些反而讓對此不熟悉的人會屢屢犯錯。 收錄一篇不錯的文章: 在C++中學習過程中,我們都知道: delete 和 new 必須 配對使用(一 一對應):delete少了,則記憶體洩露,多了麻煩更大。 Qt作為C++的庫

Java記憶體洩露記憶體溢位的區別

Java記憶體洩露與溢位的區別,這裡和大家討論一下,Java記憶體洩漏就是沒有及時清理記憶體垃圾,導致系統無法再給你提供記憶體資源(記憶體資源耗盡);而Java記憶體溢位就是你要求分配的記憶體超出了系統能給你的,系統不能滿足需求,於是產生溢位。 Java記憶體洩露與溢位

記憶體洩露記憶體溢位的區別 (概念區別 產生原因區別 及解決辦法) 個人整理

記憶體洩露和記憶體溢位的區別 概念區別 記憶體溢位 : out of memory 指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out fo memory 比如申請一個integer 但給它存了long才能存下的數那就是記憶體溢位 記憶體洩露 : memory leak 指程

Django聚合函式,F表示式Q表示式詳解

學習聚合函式的準備工作 新建一個專案,在新建一個app,名字隨意,然後在app中的models中定義幾個模型: from django.db import models # Create your models here. class Author(models.Model):

JavaScript 的記憶體洩露垃圾回收

什麼是記憶體洩露 ? 任何程式語言,在執行時都需要使用到記憶體,比如在一個函式中, var arr = [1, 2, 3, 4, 5]; 這麼一個數組,就需要記憶體。 但是,在使用了這些記憶體之後, 如果後面他們不會再被用到,但是還沒有及時釋放,這就叫做記憶體洩露(memory

記憶體洩露 記憶體溢位

記憶體溢位 out of memory,是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory; 記憶體洩漏(Memory Leak)是指程式中己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。

記憶體洩露javaScript的記憶體管理機制

1.記憶體洩漏(Memory Leak) 是指程式中己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果 2.javaScri

jsnew函式後帶括號不帶括號的區別

用new建立建構函式的例項時,通常情況下new 的建構函式後面需要帶括號(譬如:new Parent())。 有些情況下new的建構函式後帶括號和不帶括號的情況一致,譬如: function Parent(){ this.num = 1; } co

android必備記錄筆記(一)記憶體洩露各種效能優化

該篇筆記來自於平時學習時,對各種學習資源的整合,如有冒犯敬請諒解,整理的不好,還望海涵指出錯誤 一、記憶體洩露 針對記憶體洩露我認為要知道下面三點: 第一:要弄清楚記憶體洩露與記憶體溢位的區別 第二:要弄清楚常規的記憶體分析方法,重點掌握Leakcanary的使用和原

Java內部類的記憶體洩露問題

package com.example.temptemp; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.Text

android記錄筆記(一)記憶體洩露各種效能優化

該篇筆記來自於平時學習時,對各種學習資源的整合,如有冒犯敬請諒解,整理的不好,還望指出錯誤,主要用於查詢與記錄 一、記憶體洩露 針對記憶體洩露我認為要知道下面三點: 第一:要弄清楚記憶體洩露與記憶體溢位的區別 第二:要弄清楚常規的記憶體分析方法,重點掌握Leakcanary的使用

Js函式型別及宣告表示式

Js中比較有趣的恐怕就是函式型別了: function value(){ return value; } alert(typeof value); //“function” typeof是一個獲得運算元型別的操作符,列印結果是function型別,這

記憶體洩露記憶體溢位的區別

· 記憶體洩露是指程式中間動態分配了記憶體,但在程式結束時沒有釋放這部分記憶體,從而造成那部分記憶體不可用的情況,重啟計算機可以解決,但也有可能再次發生記憶體洩露,記憶體洩露和硬體沒有關係,它是由軟體設計缺陷引起的。  · 記憶體洩漏可以分為4類:  1) 常發性記憶體洩漏。發生記憶體洩漏的程式碼會被多次執

詳解java記憶體洩露如何避免記憶體洩漏

源地址:http://www.xttblog.com/?p=518 一直以來java都佔據著語言排行榜的頭把交椅。這是與java的設計密不可分的,其中最令大家喜歡的不是面向物件,而是垃圾回收機制。你只需要簡單的建立物件而不需要負責釋放空間,因為Java的垃圾回收器會負責記憶

Windows/Linux下C/C++記憶體洩露檢測工具

一 Window下的記憶體洩露檢測(以VC++環境為例) 靈活自由是C語言的一大特色,但這個特色也難以避免的帶來一些副作用,比如記憶體洩露。眾所周知,記憶體洩露的問題比較複雜,程式正常執行時你看不出它有任何異常,但長時間執行或在特定條件下特定操作重複多次時,它才暴露出來。所

Windows除錯——使用windbg查詢記憶體洩露

記憶體洩露查詢方法 C++程式設計師經常不注意記憶體使用的關閉,雖然此類問題不會導致程式邏輯問題,但隨著時間的推移,記憶體佔用量越來越多,最終導致程式崩掉。對服務端的程式,記憶體洩漏經常是致命的。 對於已經存在記憶體洩露的程式,可能Windbg查詢記憶體洩露

MySQL聚合函式count的使用效能優化

本文的環境是Windows 10,MySQL版本是5.7.12-log 一、 基本使用 count的基本作用是有兩個: 統計某個列的資料的數量; 統計結果集的行數; 用來獲取滿足條件的資料的數量。但是其中有一些與使用中印象不同的情況,比如

c語言-記憶體洩露記憶體溢位的區別

記憶體溢位     指你申請了10個位元組的空間,但是你在這個空間寫入11或以上位元組的資料,就是溢位 記憶體洩漏     指你用malloc或new申請了一塊記憶體,但是沒有通過free或delete將記憶體釋放,導致這塊記憶體一直處於佔用狀態。 #includ