new をオーバーロードしてメモリリークを自力で検出

new や malloc を自分で定義すればメモリ領域を確保する度にそれを記録することができます。前のエントリで VC++メモリリークの検出を行えるようにしてみましたが、それをとても簡略化したものを試しに自分で実装してみました。

かなりいい加減ですが(テストも全くしていません)、とりあえず動いたっぽいので載せてみます。

#include <iostream>
#include <cstdlib>
using namespace std;

struct memory_list
{
  char* file;
  int line;
  memory_list* next;

  memory_list() : file(0), next(0) {}
};
static memory_list* memory_;

inline void* operator new(size_t size, char* file, int line)
{
  void* ptr = malloc(size + sizeof(memory_list));

  memory_list* cur = (memory_list*)(ptr);
  if (!memory_) {
	memory_ = cur;
  }
  else {
	memory_list* last = memory_;
	while (last->next) {
	  last = last->next;
	}
	last->next = cur;
  }
  cur->file = file;
  cur->line = line;

  return (void*)((char*)ptr + sizeof(memory_list));
}

inline void operator delete(void* p)
{
  memory_list* cur = (memory_list*)((char*)p - sizeof(memory_list));
  if (memory_ == cur) {
	memory_ = cur->next;
  }
  else {
	for (memory_list* ptr = memory_; ptr; ptr = ptr->next) {
	  if (ptr->next == cur) {
		ptr->next = cur->next;
	  }
	}
  }

  free(cur);
}

void leak_report()
{
  for (memory_list* ptr = memory_; ptr; ptr = ptr->next) {
	cout << "File: " << ptr->file << ", Line(" << ptr->line << ")" << endl;
  }  
}

#define new new(__FILE__, __LINE__)

メモリを確保する度に memory_list という構造体に new したコードのファイル名と行番号を記録して、 delete されるまで保持します。leak_report関数でこの構造体の内容を出力しますが、プログラムの最後にこの関数を実行する事で未解放の(=リークしている)領域の存在を知る事ができます。

なお構造体のインスタンスは new する時点で要求されたサイズより大きめの領域を確保して、そこに置いています。こういう事ができるのが C/C++ のいいところだなぁと、特に C# を触った後はよく思います。悪いとこだろうと言われる事の方が多いですが……便利ですよねぇ。

int main(int, char*[])
{
  int* ptrA = new int;
  char* ptrB = new char;
  float* ptrC = new float;
  delete ptrA;
  delete ptrC;

  leak_report();

  return 0;
}

試しに上記のコードを実行してみると、以下のような出力が得られました。

user% ./a.out 
File: leak.cpp, Line(66)

66行目で char* ptrB = new char を行っています。

このような例は有り触れたものだと思いますが、自分では書いた事も読んだ事もないので(実装は今してみましたが)、もっと色々なコードを見て回りたいですね。