C++でメモリリークを検出する方法

C++@Linuxなプロジェクトでメモリリークが発生。
MemProfmtraceccmallocmpatroldmallocをチェックしたが、プロジェクトの環境で使えるのはmtraceだけだった。
しかしnew/deleteの場合はリークの存在は確認できるがソースファイル名と行番号までは出力されず(malloc/freeの場合は行番号まで出力される)。dmallocはc++のnew/deleteでも利用可能らしいのだが、いざ対象のプログラムとリンクしようとするとリンクエラーで使えない。
関連情報を調べたあげく、仕方ないので自分でコードを書いてみた。DetectMemoryLeaks.zip

内容としては、new、new[]、delete、delete[]、をオーバーライドまたはオーバーロードして、メモリの確保と解放のタイミングで対応をチェック。メモリ確保したのに解放されていない場合は、そのメモリを確保したソース上のファイル名と行番号を表示する。
1. DetectMemoryLeaks.hDetectMemoryLeaks.cpp をコピー。
2. DetectMemoryLeaks.cppをコンパイル対象に追加。
3. 最初にコンパイルされるソースに次の2行を追加。
    #include "MemoryLeakChecker.h"
    #define new new(__FILE__, __LINE__ )
4. メモリーリークをチェックしたい範囲の開始位置に次の1行を追加。
    memoryLeakCheckerCheckStart(std::cout);
5. メモリーリークをチェックしたい範囲の終了位置に次の1行を追加。
    memoryLeakCheckerCheckEnd(std::cout);
6. 対象をコンパイルして、実行すればOK。
使い方のサンプルはこちら(MemoryLeaksSample.cpp)。
注意:マルチスレッドは考慮していません。detectMemoryLeaksStart/End呼び出しの入れ子は考慮していません。

DetectMemoryLeaks.cpp の内容。

std::map detectMemoryLeaksMemoryMap;
bool detectMemoryLeaksFlag = false;

void *
operator new(std::size_t size, char * pszFileName, int nLineNum)
throw (std::bad_alloc)
{
    void * address = malloc(size);
    if(address == 0)
        throw std::bad_alloc();
   
    if(detectMemoryLeaksFlag){
        // std::cout << "new: " << pszFileName << ":" << nLineNum << " size=" << size << " address=" << address << std::endl;
        std::ostringstream oss;
        oss << pszFileName << ":" << nLineNum << " size=" << size  << " address=" << address;
        detectMemoryLeaksMemoryMap.insert( std::pair((std::size_t)address, oss.str()) );
    }
    return address;
}

void *
operator new[](std::size_t size, char * pszFileName, int nLineNum)
throw (std::bad_alloc)
{
    void * address = malloc(size);
    if(address == 0)
        throw std::bad_alloc();
   
    if(detectMemoryLeaksFlag){
        // std::cout << "new[]: " << pszFileName << ":" << nLineNum << " size=" << size << " address=" << address << std::endl;
        std::ostringstream oss;
        oss << pszFileName << ":" << nLineNum << " size=" << size  << " address=" << address;
        detectMemoryLeaksMemoryMap.insert( std::pair((std::size_t)address, oss.str()) );
    }
    return address;
}

void
operator delete(void * address)
{
    if(address == 0) // Depends on environment.
        return;
   
    if(detectMemoryLeaksFlag){
        // std::cout << "delete: " << " address=" << address << std::endl;
        std::map::iterator it = detectMemoryLeaksMemoryMap.begin();
        std::map::iterator itEnd = detectMemoryLeaksMemoryMap.end();
        std::size_t checkAddress = (std::size_t)address;
        for(; it!=itEnd; it++){
            if(it->first == checkAddress){
                detectMemoryLeaksMemoryMap.erase(it);
                break;
            }
        }
    }
    free(address);
}

void
operator delete[](void * address)
{
    if(address == 0) // Depends on environment.
        return;
   
    if(detectMemoryLeaksFlag){
        // std::cout << "delete[]: " << " address=" << address << std::endl;
        std::map::iterator it = detectMemoryLeaksMemoryMap.begin();
        std::map::iterator itEnd = detectMemoryLeaksMemoryMap.end();
        std::size_t checkAddress = (std::size_t)address;
        for(; it!=itEnd; it++){
            if(it->first == checkAddress){
                detectMemoryLeaksMemoryMap.erase(it);
                break;
            }
        }
    }
    fr
ee(address);
}

void
detectMemoryLeaksStart(std::ostream& ros)
{
    ros << "[detect memory leaks] start" << std::endl;
    detectMemoryLeaksFlag = true;
}

void
detectMemoryLeaksEnd(std::ostream& ros)
{
    ros << "[detect memory leaks] end" << std::endl;
    if(detectMemoryLeaksMemoryMap.size() > 0){
        ros << "memory leaks …" << std::endl;
        std::map::iterator it = detectMemoryLeaksMemoryMap.begin();
        std::map::iterator itEnd = detectMemoryLeaksMemoryMap.end();
        for(; it!=itEnd; it++)
            ros << "  " << it->second << std::endl;
    }else{
        ros << "memory leak is nothing." << std::endl;
    }
    detectMemoryLeaksFlag = false;
    detectMemoryLeaksMemoryMap.clear();
}

にしても、今時C++を使うのは正直どうかと。よほどメモリやCPUが限られた条件では仕方ないかもしれないが。チームでソフトを作る際にC++を使うと細かいことまで事前に決めないとぐちゃぐちゃになるし。各種ライブラリも構成やネーミングに統一感無いし。ハードの性能向上速度、メンバーの教育、コーディングルールの策定と厳守、デバッグ、可読性を考慮して、ソフトのサイクルをトータル的考えると効率が悪い。GCの最大時間が保障されたJavaVM使った方がトータルコストも下がるし安全だし効率が良いと思う。(GC領域を限定すれば時間保障は確保できるだろう。)

CDT 4.0 – Call Hierarchy

CDT4.0で待望の「Call Hierarchy」が使えるようになった。

あと、Eclipseに必要なプラグインを同梱したパッケージがいくつか提供されるようになったらしい。それにThird Party Distros紹介も。
いろいろなプラグインを個々で入れるとよくプラグイン同士のバージョンの不整合でさまざまな問題が発生するのでありがたい。
  Eclipse IDE for Java Developers, for Java EE Developers, for C/C++ Developers, Eclipse for RCP/Plug-in Developers, Eclipse Classic
  Third Party Distros: RoweBots, nexB, Innoopract