C++@Linuxなプロジェクトでメモリリークが発生。
MemProf、mtrace、ccmalloc、mpatrol、dmallocをチェックしたが、プロジェクトの環境で使えるのはmtraceだけだった。
しかしnew/deleteの場合はリークの存在は確認できるがソースファイル名と行番号までは出力されず(malloc/freeの場合は行番号まで出力される)。dmallocはc++のnew/deleteでも利用可能らしいのだが、いざ対象のプログラムとリンクしようとするとリンクエラーで使えない。
関連情報を調べたあげく、仕方ないので自分でコードを書いてみた。
内容としては、new、new[]、delete、delete[]、をオーバーライドまたはオーバーロードして、メモリの確保と解放のタイミングで対応をチェック。メモリ確保したのに解放されていない場合は、そのメモリを確保したソース上のファイル名と行番号を表示する。
1. DetectMemoryLeaks.h と DetectMemoryLeaks.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呼び出しの入れ子は考慮していません。
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領域を限定すれば時間保障は確保できるだろう。)