【C++】CRTライブラリを使用してメモリリークを検出する方法

前回のブログより、まずはCRTライブラリでのメモリ検出方法についてメモしておこうと思います。

CRTライブラリとは

"CRTライブラリ"とは「Cランタイムライブラリ」の略称で、 いろいろな機能が集まっているライブラリであり、「Visual C++」ライブラリによって実装されてるようです。

この中にメモリリークの検出機能も入っているようです。

メモリリークの検出方法

CRTライブラリを使ったメモリ検出方法は簡単。プログラムの終了直前にこの関数を実行するだけ

// ヘッダーインクルードも忘れずに
#include <stdlib.h>
#include <crtdbg.h>

int main()
{
    // メモリ確保処理など~

    _CrtDumpMemoryLeaks(); // この一行を追加する
    return 0;
}

                        

メモリリークがあった場合、こんな感じのものが出力ウィンドウに出力される

Detected memory leaks!
    Dumping objects ->
    {283} normal block at 0x00000133BC4D8800, 48 bytes long.
     Data: < 8  3    8  3   > 18 38 FF B4 33 01 00 00 1C 38 FF B4 33 01 00 00 
Object dump complete.

                        

内容はざっくりいうと、
"0x00000133BC4D8800"というメモリの場所に"48バイト"のメモリリークがあるよ~
データの中身はこんな感じだよ~、みたいな感じ。

メモリリーク箇所のプログラムの特定方法

メモリのアドレスを言われてもどのプログラムが原因か特定できないので、これを特定する方法がおおむね2つあります。

_CrtSetBreakAlloc()関数を使用する

メモリ検出メッセージの
{283} normal block at 0x00000133BC4D8800, 48 bytes long.
今回の場合この283という数字をこの関数の引数として渡すことで、メモリリークが発生しているプログラムの箇所に動的にブレークポイントを設定することができます。

// 省略
int main()
{
    _CrtSetBreakAlloc(283); // 必ずプログラムの最初に実行する

    // メモリ確保処理など~

    _CrtDumpMemoryLeaks();
    return 0;
}

                            

もう一つの方法

もう1つの方法として、メモリリークが疑われるソースコードの先頭行に以下のマクロを追加することでも特定することができます。


#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)

                        

この方法を使うと、Unityでも見たことのあるようなわかりやすい文で出力してくれます。

Detected memory leaks!
Dumping objects ->
C:\~path\main.cpp(119) : {283} normal block at 0x000002784B8EC970, 48 bytes long.
 Data: <  ,Dx     ,Dx   > E8 B0 2C 44 78 02 00 00 EC B0 2C 44 78 02 00 00 

                        

この場合だと、「main.cppの119行目でメモリが確保されたものが解放されていないですよ」というメッセージになります。

注意すべきなのは、場所によっては名称エラーが出てしまうことがあったので、ものによってはこの方法は使用できない可能性があります。
(もしかしたら僕の使い方が悪かっただけかも?原因が分かり次第追記したいと思います。)

メモリ開放は忘れずにしましょう!

追記:2025/02/19

おそらく原因が判明したので追記します。

そもそもこの処理はメモリを確保する際の「new」という文にマクロを追加する処理なのですが、
このマクロを追加したヘッダファイル、またはそれをインクルードしたファイルで別のライブラリをインクルードすると、
別ライブラリ内で「new」という文字列を使用した関係ないマクロにまで影響が出てしまって、コンパイル時に名称エラーが発生してしまうようです。

実際にMicrosoftのドキュメントにも注意書きがありました。
ドキュメント より

!注意
new という名前のプリプロセッサ マクロやその他の言語キーワードは作成しないことをお勧めします。

解決法については、自分のプロジェクト用のnamespaceを作成し、その中で定義して別ライブラリと接触しないようにすれば名称エラーはなくなります。

namespace SampleProject
{
    #ifdef _DEBUG   // デバック時にのみ実行
        #define _CRTDBG_MAP_ALLOC
        #include <cstdlib>
        #include <crtdbg.h>

        // メモリリーク箇所を出力するマクロ
        #define new new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
    #endif
}

                

また、もし「new」という文字列の入ったマクロを別で使用している場合には、専用の「newマクロ」を定義すれば同じく活用できます。
こちらはドキュメントのほうにも紹介されています。

namespace SampleProject
{
    #ifdef _DEBUG   // デバック時にのみ実行
        
        // 省略

        #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
    #endif

    //===使用例===================

    int* arr = DBG_NEW int[4];
}

                

また「#ifdef _DEBUG」マクロを使用することによって、Debugビルド時にのみ実行されるようになるためより良くなるでしょう。

2024/08/27

ブログ記事一覧に戻る