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領域を限定すれば時間保障は確保できるだろう。)

ソフトウェアとは

ソフトウェアの仕様書は料理のレシピに似ている
とても分かりやすくて的確な例えだ。未だに、柔軟性と変化度がまったく違う物質的な建築やモノの製造や生産とソフトを一緒くたにしている人が多いようなので、そういった人には丁度良い説明だろう。
新しい物事は暗黙のお互いの共通知にどれくらいズレがあるかを認識することから始めないと。

優秀なエンジニアは「入社時のスキルを問わない会社」には就職してはいけない
どうも、日本企業は基礎能力(記憶力と論理的思考力=学力)が高い人間を採用して社内で教育する方針ばかりのようで。実際使う方の立場からは既に底上げされて最低限保障されて部下を使う方が楽だろうが、昔みたいに新人が多い時代ではないし、受けてきた教育も違うし、そんな品質の部下は揃わないんじゃないかな。揃ったとしても後述する理由により辞めていくだろう。それに、ソフト業界の場合は情報がオープンなので、日々の目の前の仕事で忙しいサラリーマンより、学生の方が最新の技術を会得していたり。もちろん、仕事として任務を遂行していく能力は学生には足りないので、それは研修なりなんなり必要だろう。

品質やユーザビリティの保持や向上の点から見ると、特に良いモノつくりにおいて一番重要なのは開発者の適切なこだわりや執念、誇りだったりする。基礎能力がある人が単に仕事としてこなしてるのと、趣味とかぶる人が好きでやっているのでは大きな差が出る。両方の素質を持つ人が一番だが、そんな人は多くはないのではないか。

そして、ベルトコンベア式に同じことを繰り返して作れるソフトは成熟した(枯れた)分野限定である。商売の対象としては大きく見積もりが外れることもなく商売としては比較的安全である。しかし、寡占化が進むので利益は減り、薄利多売になり、ビジネスとしての総利益は少なくなっていく。そしてイノベーションも無い。つまり、人としての開発者にとって面白みは無い。

人を人として採るのか、人をロボットとして採るのか、この点で企業側が嘘をついていると、雇う方も雇われる方も上手くいかないのではないだろうか。

基礎能力が高い人間を採用したのに、つまらない仕事をさせていたら、定着率が悪いのも仕方ないだろう。もちろん前から言われているようにマッチングも重要。逆に基礎能力が高くないのに「IT系って儲かるらしい → 今までやったことない(*1)けど就職してみよう」という思考でこの業界に入った人はロボットとしてIT土方になるか、消耗して他の業界に非難するか。最近はこの儲かるという幻想も少なくなってきたようだし。ようやく学生側が業界の現状に適切な判断ができる状態になりつつあるのだろうなぁ。

(*1) 昔と違って今はPCやPGに触れられる機会は多い。就職前までに触れていなかったとしたら、適性が無いか、偶然それまで興味が無かったかだろう。

まぁ。ソフト業界の場合も一部は成熟期に入ってきたので他の業界同様に職業名で中身は語れなくなったということですかね。

Google Gadget – ピンポイント天気予報 高負荷対応

お知らせが遅くなりましたが、実は10/14にガジェットを更新しました。

  • サーバの高負荷対応
  • アフィリエイト追加

高負荷に対応するために裏方の仕組みを変更しました。
その関係で天気情報が更新されるタイミングのリアルタイム性は少し落ちています。
サーバが2001年製の小型PCでスペック的に厳しいのが理由です。今時のマシンにリプレースすれば解決するのですが。。
という訳でアフィリエイトを追加しました。同じものを買う時はここから買って頂けると助かります。

最後に今後の更新予定を。

  • アイコンのセンタリング (IE対応)
  • 言語の手動指定 (今はユーザの環境に合わせて英語と日本語が切り替わるようになっています。)
  • 韓国語対応
  • 中国語対応
  • 警報や注意報がある場合は左下に表示
  • タブ化 (スペースを考慮し、有効/無効可能に)
  • アメダス画像(気温、降水量、風向/風速、日照時間、積雪深)