Take me to source code
IntroductionIt is straightforward nowadays to write C++ code that is “free” of memory leaks without having to give up the use of pointers, by using smart pointer classes such as std::auto_ptr or any of the boost family of pointers such as boost::shared_ptr. However, complex software, or legacy code may still contain memory allocation bugs, which are traditionally quite hard to detect and fix. Memory debugging libraries or programs can be used to debug such issues, but they usually provide low level information which needs to be further interpreted in the context of the application to be useful. The utility classes presented here can be used to do object level allocation tracking, thus offering a higher level view of the object creation and destruction behavior. They require minimal code changes and have very little memory usage or performance impact. Furthermore, they are disabled in release mode. Detecting object leaksHere is a typical usage example: Without object counter: class C With object counter: class C Once added to a class, the object counter will identify each new instance of that class by an integer value, or id (unique for all objects of a class) that will be stored in a per class static container for tracking. When an instance is destructed, the corresponding id will be removed from the container. If all instances of a class have been destructed upon application exit, a success message is logged. If there are still objects that haven’t been destructed, an object leak message is logged, indicating the class name and some extra information about the current status: the number of instances still in existence, the maximum number of instances at any one time, as well as a list of ids of leaked objects. An assert is triggered if the application attempts to destruct an object with an id that is not found, usually indicating that an object is destructed multiple times. Debugging object leaksThe object counter provides code to help with debugging object creation/destruction issues as well. If object leaks have been detected it is useful to run the application several times and determine if there is a pattern in the number and indexes of the leaked objects. If an object with the same id between application run is leaked, a second object counter macro can be used to break when this specific instance is created. For example, if the leaked object id is 5, the usage is as follows: class C When the class C instance index 5 is created, the execution breaks into the debugger by using the __asm int 3; (or DebugBreak() ) statement. It is then possible to examine the context, call stack etc to find the reason for the leak. NotesCharacter typeThe object counter classes are character type neutral, meaning they work as well with ASCII, UTF8 or UTF16 (Unicode) character types, by using the character macros (_T, TCHAR) defined in the compiler header files. Instead of using std::string or std::wstring directly, we defined a template based string type that takes the current character type TCHAR as argument. Multi-threaded executionThe current implementation works well in a single-threaded environment (the application can be multithreaded, but the objects themselves should be created/destructed within the same thread). If the objects being tracked are created/destructed from multiple threads the object counter classes will require thread synchronization to work properly. The object counter classes make use of a placeholder Lockable class, which in a real multi-threaded application will have to implement the synchronization methods. LoggingStatic instancesThe object counter utility classes rely on the fact that static variables are destructed when the process exits, which ensures the object counting works well with heap (allocated using new) and local variables. If however the instances being tracked are themselves static, the object counter utility may not work anymore, as C++ doesn’t guarantee the order in which static variables are allocated or destructed. It is therefore possible that the tracking static variable be destructed before the tracked instances, thus detecting false leaks. String << operatorThe STL std::ostringstream class is very useful when assembling strings from variables of different types, without having to do any explicit conversions, by using the << operator. Using this class however requires several extra steps, for example: int n = 10; ostringstream_t os; os << _T( "variable int
n is " ) << n; OutputDebugString( os.str().c_str() ); We have defined a template based << operator that is applied to directly to strings and has the same effect as the std::ostringstream << operator. This makes it possible to create strings in place from values of various types, thus making the code and clearer (for those who like compact code). The same example using the string_t << operator: int n = 10; OutputDebugString( (string_t() << _T( "variable int n is " ) <<
n).c_str() ); We are using this where logging strings are created from string and integer variables. objcounter.h#pragma once #ifdef _DEBUG #include <fstream> #include <set> namespace tradery { class Counter { private: int m_count; tradery::t_string m_name; std::set< int > m_indexes; // total number of instances created int m_total; int m_max; tradery::Mutex m_mx; void setName( const tradery::t_string& name ) { if( m_name.empty() ) m_name = name; } public: Counter() : m_count( 0 ), m_total( 0 ), m_max( 0 ) { } ~Counter() { tradery::Lock lock( m_mx ); assert( m_indexes.size() == m_count ); if( m_count > 0 ) { LOG( log_error, "\n!! " << m_name << "leak\n\tTotal created:\t " << m_total << "\n\tMax created:\t " << m_max << "\n\tLeaked count:\t " << m_count << "\n\tLeaked indexes:\t " << leakedIxsAsString() ); } else if( m_count < 0 ) assert( false ); else { // reached count 0 - all instances have been destroyed // todo: we may want to log this when debugging memory/object leaks } } int inc( const tradery::t_string& name, int breakIndex ) { tradery::Lock lock( m_mx ); setName( name ); ++m_total; ++m_count; // latest removed is used to ensure that we don't get a collision // in case an earlier index is removed before a later index int ix = m_total; m_max = m_total; m_indexes.insert( ix ); if( ix == breakIndex ) { __asm int 3; } // LOG( log_debug, "ObjCounter - adding \"" << m_name << "\", index " << ix << ", total " << m_total ); return ix; } void removeIndex( int index ) { tradery::Lock lock( m_mx ); --m_count; if( m_indexes.find( index ) == m_indexes.end() ) { // this indes has already been deleted - error assert( false ); // LOG( log_debug, "ObjCounter - \"" << m_name << "\" attempting to remove not found index (deleting twice?)\n\tindex:\t " << index << "\n\tcurrent ix map size:\t" << m_indexes.size() ); } else { m_indexes.erase( index ); // LOG( log_debug, "ObjCounter - removing \"" << m_name << "\", index " << index << ", remaining " << m_indexes.size() ); } } private: #define MAX_LEAKED_INDEXES_DISPLAYED 20 tradery::t_string leakedIxsAsString() { tradery::t_string leakedIxs; int n = 0; for( std::set< int >::const_iterator i = m_indexes.begin(); i != m_indexes.end() && n < MAX_LEAKED_INDEXES_DISPLAYED; ++i, ++n ) leakedIxs << ( n == 0 ? _T( "" ) : _T( ", " ) ) << *i; if( n >= MAX_LEAKED_INDEXES_DISPLAYED ) leakedIxs << _T( ", ..." ); return leakedIxs; } }; template< typename T > class ObjCounterBase { int m_objIx; static Counter m_count; public: ObjCounterBase( const tradery::t_string& name, int breakIndex ) { m_objIx = m_count.inc( name, breakIndex ); } // this is to make sure that when assigning an object of one class to another // it won't change the object index (otherwise the default operator= would set the m_objIx) ObjCounterBase< T >& operator=( const ObjCounterBase& c ) { return *this; } virtual ~ObjCounterBase() { m_count.removeIndex( m_objIx ); } }; } // namespace tradery template< typename T > tradery::Counter tradery::ObjCounterBase< T >::m_count; #define OBJ_CTR( COUNTER_STR, BREAK_INDEX ) \ \ class ObjCounter : public tradery::ObjCounterBase< COUNTER_STR > \ { \ public: \ ObjCounter() \ : tradery::ObjCounterBase< COUNTER_STR >( _T( #COUNTER_STR ), BREAK_INDEX ) \ { \ } \ };\ #define OBJ_COUNTER( COUNTER_STR ) \ OBJ_CTR( COUNTER_STR, 0 )\ ObjCounter m_objCounter; #define OBJ_COUNTER_BREAK_ON_INDEX( COUNTER_STR, BREAK_INDEX ) \ OBJ_CTR( COUNTER_STR, BREAK_INDEX ) \ ObjCounter m_objCounter; #else #define OBJ_COUNTER( COUNTER_STR ) #define OBJ_COUNTER_BREAK_ON_INDEX( COUNTER_STR, BREAK_INDEX ) #endif
|
||||||||||