C++でのメモリ割り当て、特にnew
/delete
によって行われたメモリ割り当てを追跡するにはどうすればよいですか。オブジェクトの場合、operator new
を簡単にオーバーライドできますが、すべての割り当てをグローバルにオーバーライドして、カスタムnew
/delete
を通過する方法がわかりません。これは大きな問題ではないはずですが、これがどのように行われるのかわかりません(#define new MY_NEW
?)。
これが機能するとすぐに、割り当てのポインタ/場所のどこかにマップがあれば十分だと思います。そのため、現在「アクティブ」であるすべての割り当てを追跡し、アプリケーションの最後に割り当てを確認できます。解放されていない。
まあ、これも確かに少なくとも数回行われているように見えるので、そこに良いライブラリ(できればポータブルライブラリ)はありますか?
Linuxにはvalgrind
を使用することをお勧めします。未割り当てメモリへの書き込みなどのバグの中でも、解放されていないメモリをキャッチします。もう1つのオプションはmudflapです。これは、メモリが解放されていないことも示します。 gccで-fmudflap -lmudflap
オプションを使用してから、MUDFLAP_OPTIONS=-print-leaks ./my_program
でプログラムを開始します。
これが非常に単純なコードです。高度な追跡には適していませんが、自分で実装する場合、原則としてどのように実行するかを示すことを目的としています。このようなもの(登録されたnew_handlerおよびその他の詳細を呼び出すものを省略)。
template<typename T>
struct track_alloc : std::allocator<T> {
typedef typename std::allocator<T>::pointer pointer;
typedef typename std::allocator<T>::size_type size_type;
template<typename U>
struct rebind {
typedef track_alloc<U> other;
};
track_alloc() {}
template<typename U>
track_alloc(track_alloc<U> const& u)
:std::allocator<T>(u) {}
pointer allocate(size_type size,
std::allocator<void>::const_pointer = 0) {
void * p = std::malloc(size * sizeof(T));
if(p == 0) {
throw std::bad_alloc();
}
return static_cast<pointer>(p);
}
void deallocate(pointer p, size_type) {
std::free(p);
}
};
typedef std::map< void*, std::size_t, std::less<void*>,
track_alloc< std::pair<void* const, std::size_t> > > track_type;
struct track_printer {
track_type * track;
track_printer(track_type * track):track(track) {}
~track_printer() {
track_type::const_iterator it = track->begin();
while(it != track->end()) {
std::cerr << "TRACK: leaked at " << it->first << ", "
<< it->second << " bytes\n";
++it;
}
}
};
track_type * get_map() {
// don't use normal new to avoid infinite recursion.
static track_type * track = new (std::malloc(sizeof *track))
track_type;
static track_printer printer(track);
return track;
}
void * operator new(std::size_t size) throw(std::bad_alloc) {
// we are required to return non-null
void * mem = std::malloc(size == 0 ? 1 : size);
if(mem == 0) {
throw std::bad_alloc();
}
(*get_map())[mem] = size;
return mem;
}
void operator delete(void * mem) throw() {
if(get_map()->erase(mem) == 0) {
// this indicates a serious bug
std::cerr << "bug: memory at "
<< mem << " wasn't allocated by us\n";
}
std::free(mem);
}
int main() {
std::string *s = new std::string;
// will print something like: TRACK: leaked at 0x9564008, 4 bytes
}
標準のアロケーターはオーバーライドされた演算子newを使用するため、マップには独自のアロケーターを使用する必要があります。これにより、無限の再帰が発生します。
演算子newをオーバーライドする場合は、マップを使用して割り当てを登録してください。 newの配置フォームによって割り当てられたメモリを削除すると、その削除演算子も使用されるため、不明なコードでマップを使用せずに演算子newがオーバーロードされた場合、演算子deleteは割り当てられていないことを通知し、 std::free
を使用してメモリを解放します。
また、Paxが彼の解決策についても指摘しているように、これは、独自に定義した演算子new/deleteを使用したコードによって引き起こされたリークのみを表示することに注意してください。したがって、それらを使用する場合は、それらの宣言をヘッダーに入れて、監視する必要があるすべてのファイルに含めます。
具体的には、valgrindのmassifツールを使用します。 memcheckとは対照的に、massifはメモリの不正使用には関心がありませんが、時間の経過とともに割り当てを追跡します。これは、プログラムのヒープメモリ使用量を「効率的に」測定するのに適しています。最良の部分は、コードを書く必要がないことです。試してみてください:
http://valgrind.org/docs/manual/ms-manual.html
またはあなたが本当にせっかちな場合:
valgrind --tool=massif <executable> <args>
ms_print massif.out.<pid> | less
これにより、時間の経過に伴う割り当てのグラフと、大きな割り当てが発生した場所までのバックトレースが得られます。このツールはLinuxで実行するのが最適ですが、Windowsのバリエーションがあるかどうかはわかりません。それはOS Xで動作します。
幸運を!
http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml にあるコードを使用するには、次の変更を加えます。指定されたコードは、大きなhonkin 'ソースファイルが1つある場合にのみ機能します。 SO( ここ )に関する別の質問のためにこれを整理しました。
まず、stdafx.hを変更しない、独自のファイルに変更を加えます。
別のヘッダーファイルmymemory.hを作成し、その中に関数プロトタイプを配置します(たとえば、これにはbodyがないことに注意してください)。
inline void * __cdecl operator new(unsigned int size,
const char *file, int line);
また、そのヘッダーに、AddTrack()、DumpUnfreed()などの他のプロトタイプ、および#defines、typedef、externステートメントを配置します。
extern AllocList *allocList;
次に、新しいmymemory.cpp(#includeのmymemory.hも含む)に、allocListの実際の定義を(プロトタイプだけでなく)すべての実際の関数と一緒に配置し、そのファイルをプロジェクトに追加します。
次に、#include "mymemory.h"
メモリを追跡する必要があるすべてのソースファイル(おそらくそれらすべて)。ヘッダーファイルに定義がないため、リンク中に重複が発生することはなく、宣言が存在するため、未定義の参照も取得されません。
これは、コンパイルしないコード(サードパーティのライブラリなど)のメモリリークを追跡しませんが、自分の問題について通知する必要があることに注意してください。
グローバル演算子のnewとdeleteを再実装して、必要な機能を提供することもできますが、プラットフォームの制限などにより、これがメモリ割り当てを追跡する唯一の方法でない限り、これはお勧めしません。
メモリデバッガは、ほとんどの一般的な開発プラットフォームで利用できます。 WindowsやさまざまなUnixで動作する商用ソリューションについては PurifyPlus を、で動作するオープンソースソリューションについては valgrind をご覧ください。 Linux(および潜在的に他のオペレーティングシステムですが、私はこれまでLinuxでしか使用していません)。
グローバル演算子を置き換える場合は、 この記事 を参照してください。
Windowsで開発する場合、無料のツールDebugDiagは、メモリの検索とリークの処理に役立ちます。
DebugDiagを機能させるために、プログラムを拡張する必要はありません。
使用するのが最も簡単または最も直感的なプログラムではありませんが!あなたがそれを使用する方法についてのチュートリアルと指示のためにグーグルすることを確認してください。
WindowsプラットフォームのC++プロジェクトでは、VLD(Visual Leak Detector)を使用します。これは、アプリケーションの終了時にメモリリークを追跡および報告する、実装が非常に簡単です。システムは、さまざまな方法(ディスクロガー、IDE、XMLなど)でレポートするように設定でき、常にデバッグが難しいWindowsサービスのリークを検出するのに非常に役立ちます。したがって、ポータブルソリューションを探しているときに、独自のソリューションを作成したい場合は、もちろん、ガイダンスのソースを表示できます。それが役に立てば幸い。
サイトを引用するには:
これは、C/C++アプリケーションのメモリリークをすばやく診断して修正するための非常に効果的な方法です。
Linuxには、少なくとも2つの従来の方法があります。
そのため、newとdeleteをキャッチするだけでなく、Cスタイルのメモリ割り当て関数もキャッチします。私はまだWindowsでこれを行っていませんが、DLLがそこでリンクされる方法を書き直す方法を見てきました(私はそれらがちょっと不器用だったことを思い出しますが)。
ただし、これらが興味深い手法であるという事実は別として、valgrindを使用して何よりもやりたいことを実行することをお勧めします。
質問に直接答えるわけではありませんが、プログラムの最後にリークされたヒープオブジェクトのリストを取得したいだけの場合は、 valgrind を指定してプログラムを実行することをお勧めします。
MS VSの場合、 Debug CRT Heap で遊ぶことができます。 valgrindほど単純ではなく、ここで説明するには少し多すぎますが、必要なことを実行できる場合があります。
これをプログラミング演習として行う場合は、代わりに独自のスマートポインタークラスを作成し、この1つのプロジェクト(またはプロジェクトのモジュール)全体で一貫して使用することで、より多くの洞察が得られる可能性があります。
この小さな便利なコードを確認してください。new
の代わりにNEW
を使用し、NewHelper
コンストラクターですべての割り当てを追跡します。
#include <iostream>
class NewHelper
{
private :
void* addr = nullptr;
public :
NewHelper(void * addr_)
{
addr = addr_;
std::cout<<addr<<std::endl;
}
template <class T>
operator T ()
{
return (T)addr;
}
};
#define NEW (NewHelper)(void*)new
int main()
{
int * i = NEW int(0);
return 0;
}
ツールが必要な場合は、通常、コンパイラ/標準ライブラリが提供するものから始めます。
Linuxで開発している場合、これに最適なツールの1つ(メモリリークの検出、コードの特定の場所で行われた割り当ての追跡など)は、valgrind、特にそのmassifツールです。唯一の欠点は、プログラムの実行速度が遅い(またははるかに遅い)ため、デバッグにのみ役立つことです。
このlinkで指定されたヘッダーファイル(MemTracker.h)をに追加することができますCおよびC++でメモリの割り当て/割り当て解除を追跡するソリューション。メモリリークがあるかどうか、およびどのコード行が原因であるかを示します。
他の多くの回答が、使用できるツールに焦点を当てていることに気づきました。私はそれらのいくつかを使用しました、そしてそれらは大いに役立ちます。
ただし、プログラミングの演習として、c ++で作業していることを確認すると、グローバルなnewとdelete、malloc、free、reallocをオーバーライドする必要があります。 newとdeleteをオーバーライドするだけで十分だと思うかもしれませんが、std :: stringやその他のクラスはmalloc、特にreallocを使用する可能性があります。
次に、これを配置したら、ヘッダーの追加を開始して、メモリの上書きをチェックしたり、割り当てごとにスタックトレースを記録したりできます。
全体として、ここに記載されているツールの1つを使用することをお勧めしますが、独自のシステムを作成するのは楽しいかもしれません。
安くはありませんが、C++の時代には、リークやその他のメモリの問題をデバッグするのに purify が最適なツールであることがわかりました(現在はIBMが所有しているため、サポートは低下しました)。 Bounds Checker 一部の人に好かれましたが、私が開発していたソフトウェアではうまく機能しませんでした。