// C++ tip of the week #11 // Topic: Catching memory leaks (1) // Relevance: debugging // #if defined( _MSC_VER ) && !defined( HAVE_NEW_IOSTREAMS ) #define HAVE_NEW_IOSTREAMS #endif #if __GNUC__ >= 3 #define HAVE_NEW_IOSTREAMS #endif #ifdef HAVE_NEW_IOSTREAMS #include #else #include #define std #endif #ifndef NDEBUG #include #include #include // pointers to objects allocated on the heap will be stored, together with // file name and line info of where they were 'new-ed' typedef std::map< void*, std::pair< const char*, int > > ObjLocMap_t; // helper class to count heap based objects class ObjectCounter { const char* _szClassName; // class name for which the counter is counting public: explicit ObjectCounter( const char* szClassName ); ~ObjectCounter(); int _nCount; // separate object count ObjLocMap_t _mapObjectWasHere; // only if overloaded operator new is used }; ObjectCounter::ObjectCounter( const char* szClassName ) : _nCount( 0 ), _szClassName( szClassName ) {} // upon destruction, ObjectCounter 'detects' if any objects are not destroyed, // printf is used, as on some platforms std::cout might already be destroyed (2) ObjectCounter::~ObjectCounter() { assert( _nCount >= _mapObjectWasHere.size() ); if ( _nCount ) { printf( "%s: %d objects leaked!\n", _szClassName, _nCount ); } if ( _mapObjectWasHere.size() ) { printf( "These objects where allocated in:\n"); for ( ObjLocMap_t::iterator objIdx = _mapObjectWasHere.begin(); objIdx != _mapObjectWasHere.end(); ++objIdx ) { printf( " file: %s at line: %d\n", objIdx->second.first, objIdx->second.second ); } } } // this trace strategy depends on ObjectCounter to be destroyed only after all // heap based objects of 'Class' are destroyed; note also, that 'Class', not // ObjectCounter, manages the object count #define DECLARE_TRACE_OBJECTS( Class ) \ static ObjectCounter W130798Count; /* weird name, prevents conflicts */ \ public: \ /* see ctotw8.cxx ("Memory Pool") for comments on class specific memory allocation and deallocation operators */ \ void* operator new( size_t size ) { \ assert( sizeof( Class ) == size ); \ ++W130798Count._nCount; /* one new heap based object */ \ return ::operator new( size ); \ } \ void* operator new( size_t size, const char* str, int l ) { \ void* p = Class::operator new( size ); /* updates count */ \ ObjLocMap_t& theMap = W130798Count._mapObjectWasHere; \ assert( theMap.end() == theMap.find( p ) ); \ theMap[ p ] = std::make_pair< const char*, int >( str, l ); \ return p; \ } \ void* operator new( size_t, void* p ) throw() { /* placement new */ \ /* no W130798Count._nCount++, as no memory is allocated */ \ return p; \ } \ void operator delete( void* p, size_t size ) { \ if ( p == 0 ) return; \ assert( sizeof( Class ) == size ); \ --W130798Count._nCount; /* one heap based object less */ \ ObjLocMap_t& theMap = W130798Count._mapObjectWasHere; \ ObjLocMap_t::iterator objIdx; \ /* objects need not be logged in the object location map */ \ if ( theMap.end() != ( objIdx = theMap.find( p ) ) ) { \ theMap.erase( objIdx ); \ } \ ::operator delete( p ); \ } // the ObjectCounter is static, thus it needs to be defined (3) #define IMPLEMENT_TRACE_OBJECTS( Class ) \ ObjectCounter Class::W130798Count = ObjectCounter( #Class ); #else // NDEBUG // if in production mode, the tracing code is removed #define DECLARE_TRACE_OBJECTS( Class ) #define IMPLEMENT_TRACE_OBJECTS( Class ) #endif // !NDEBUG class Test { DECLARE_TRACE_OBJECTS( Test ) public: Test() { std::cout << "A Test object was created." << std::endl; } ~Test() { std::cout << "A Test object was destroyed." << std::endl; } }; IMPLEMENT_TRACE_OBJECTS( Test ) // this macro need not be used if heap objects need only be counted; it should // be added after all header files only #ifndef NDEBUG #define new new( __FILE__, __LINE__ ) #endif // This program will show the use of the above defined memory leak catching // macro's by explicitly leaking a heap based object. int main() { Test myFirstTest; Test* pMySecondTest = new Test; // (4) Test* pMyThirdTest = new Test; delete pMyThirdTest; // (4) return 0; } // (1) In general, professional tools such as Insure++ or the 'built-in' // tools of your IDE should be preferred over home crafted gadgets like these // macro's. This example is however instructive, as memory leak detection tools // employ similar techniques. // // (2) Which is not standard conforming behaviour, see footnote #265. // // (3) For classes inside namespaces the fully qualified name needs to be used, // for templates a special macro needs to be written which adds the // template< class T > upfront. // // (4) The tracing scheme is easily defeated by using ::new or ::delete.