内存管理器的核心思想是对标准的new和delete进行重载,并使用#define来定义一些自定义的函数。通过重载内存分配和释放例程,可以使我们自己内存跟踪模块替换为标准例程。这些例程将记录请求分配的内存所在文件的行号。并记录统计信息。
首先需要重新创建重载new和delete运算符。正如前面指出的,我们将记录请求内存分配的代码所在的文件和行号。这些信息发现内存泄漏至关重要。通过这些信息,可以找到内存分配的发生位置。
inline void* operator new(size_t size,const char*file, int line);
inline void* operator new[](size_t size,const char*file, int line);
inline void* operator delete(void address);
inline void* operator delete[](void address);
需要注意在这里,为确保正确的运行,必须对运算符new和delete的标准版本和数组版本都进行重载。
#define new new( __FILE__, __LINE__ )
#define delete (setOwner( __FILE__, __LINE__ ), false) ? setOwner( "", 0 ) : delete
#define malloc(sz) AllocateMemory( __FILE__, __LINE__, sz, MM_MALLOC )
#define calloc(num, sz) AllocateMemory( __FILE__, __LINE__, sz*num, MM_CALLOC )
#define realloc(ptr, sz) AllocateMemory( __FILE__, __LINE__, sz, MM_REALLOC, ptr )
#define free(sz) deAllocateMemory( __FILE__, __LINE__, sz, MM_FREE )
#dfine new 语句把所有的new替换为new例程不仅仅分配的内存量,还包含文件名和行号,以便跟踪内存分配的情况。
内存管理器的记录工作
提供了将标准的分配例程替换为我们自己例程的框架后,便可以开始进行记录。我们查找关于内存泄漏的工作,边界违例的情况以及实际的内存需求。为记录所需的所有信息,必须首选一个数据结构来存储与内存分配的相关信息,为提高效率和速度,我们使用一个链式哈希表。其中每一个哈希表条目都包含一下信息;
struct MemoryNode
{
size_t actualSize;//实际分配大小
size_t reportedSize;//实际 + 记录信息总大小
void* actualAddress;
void* reportedAddress;
char sourceFile[30];
unsigned short sourceLine;
unsigned short paddingSize;
char options;
long predefineBody;//预定义固定值 判断内存泄漏
ALLOC_TYPE allocationType;
MemoryNode *next,*prev;
};
该结构存储了分配给用户的内存数量,还存储了分配的内存快前后的补白(padding)内存量。我们还记录了分配类型,以防止分配与释放之间的不匹配。例如,如果分配内存时使用的时运算符new[],而释放内存时使用的运算符delete,而不是delete[],则可能由于没有调用对象的析构函数而导致内存泄漏。
现在,我们有了确定程序中是否有内存泄漏所需的所有信息。通过AllocateMemory()例程中创建一个MemoryNode,并将其插入到哈希表中,可以记录分配的所有内存,然后通过deAllocate()中删除MemoryNode,可以确保哈希表只记录当前分配的内存。如果在退出程序时,哈希表不为空,则说明发生了内存泄漏。正如之前所说,在deAllocateMemory()例程中,我们还将检查用于分配内存的方法是否与用于释放的内存匹配,如果不匹配,我们将指出潜在的内存泄漏。
接下来将收集边界违例的信息。当应用程序使用的内存超过分配给他的内存时,将发生边界违例。最容易出现这种情况的地方是访问数组含10个元素,而访问第11个元素,则超越数组边界,重写或访问不属于该数组的信息。为防止这种情况。我们将在分配内存的前后提供补白。因此,如果一个例程请求分配5byte的内存。AllocMemory()实际分配了5 + sizeof(long)* 2*paddingSize(byte).我们使用long来填充。接下来,我们将初始化预定义值,如0xDEADC0DE.这样释放内存的时候,我们可以对补白进行检查。其值不是预定义的值,则说明了边界违例。这种情况下,我们查询相对应的MemoryNode,并将违例情况告知用户。
需要收集的最后一项是程序的内存需求。我们希望分配了多少内存,其中国被实际使用的内存有多少和分配的最大内存量。为收集这些信息,我们需要另外一个容器。下面代码列举类和相关成员。
class MemoryManager
{
public:
unsigned int m_totalMemorylocations;
unsigned int m_totalMemoryAllocated;
unsigned int m_totalMemoryMemoryUsed;
unsigned int m_peakMemoryAllocation;
};
在AllocatedMemory()中,我们将能够更新MemoryManager中除变量m_totalMemoryUsed之外的所有信息。要确定分配内存中实际被使用的有多少,需要采用与用于确定边界违例的方法类似的技巧,通过对AllocateMemory()例程中将内存初始化一个预定的值,并在释放内存时查询其中的值,可以知道实际被使用的内存有多少。为获得更好的结果,我们还使用long值来初始化32bit的内存边界,并使用一定的预定直来进行初始化,对于不处于32bit的边界内的其他字节,将被初始化为0xE。
报告信息
一旦程序启用了该内存管理器,并运行该程序,则在退出程序之前,将生成一个日志文件,其包含所有的内存泄漏,边界违例和最终的统计报告。
最后一个问题是:我们如何知道程序何时将终止,以便输出日志信息?一种简单的解决方案是,要求程序员在程序终止之前显示地调用dumpLogReport(),然而,这违背了创建无缝接口的原则,为在不使用显试函数调用的情况下,确定程序何时将终止,我们将使用一个静态类实例。
class Initialize
{ public: Initialize() { InitializeMemoryManager(); } };
static Initialize InitMemoryManager;
bool InitializeMemoryManager(){
static bool hasBeenInitialized = false;
if ( s_manager ){
return true;
}
else if (hasBeenInitialized ){
return false;
}else {
s_manager = (MemoryManager*)malloc( sizeof(MemoryManager) );
s_manager->initialize();
atexit( releaseMemoryManager ); // Log this function to be called upon program shut down.
hasBeenInitialized = true;
return true;
}
}
void releaseMemoryManager(){
NumAllocations = s_manager->m_numAllocations;
s_manager->release(); // Dump the log report and free remaining memory.
free( s_manager );
s_manager = NULL;
}
我们将确保的问题是,确保内存管理器是第一个被创建的对象,同时也是最后一个被释放的对象,由于静态第定义对象被处理顺序,这很困难。例如我们在创建内存管理器对象之前创建一个静态对象,而后者的构造函数动态地分配内存,则内存管理器将无法跟踪这些内存,同样,如果我们使用::atexit()方法来调用一个负责释放内存的方法,则::atexit()方法被调用之前,内存管理器对象已被是释放,因此指出内存泄漏情况将是错误的。
为解决这些问题,需要做以下改进,首先,通过在内存管理器的头文件中创建InitMemoryManager对象,可确保它在任何静态对象声明之前被创建。在任何静态定义之前将内存管理器头文件包含进来时,情况也是如此。Microsoft指出,静态对象被创建的顺序与出现顺序相同,而被释放则与此相反。齐次,为了确保内存管理器始终可用,我们在AllocateMemory()和DeallocateMemory()中调用InitializeMemory(),从而保证内存管理器处于活跃状态。
注意事项
要跟踪内存,需要占用内存和CPU时间,另外还有其他几个细节需要注意,首先,必须处理包含其他文件时可能导致的语法错误,在某些情况下,导致语法错误的原因可能是其他文件重新定义了运算符new和delete。使用STL实现时,尤其容易出现这种情况。例如我们包含MemoryManager.h.然后在包含<map>,则导致各种类型错误,要解决这种问题,我们将使用其他两个头文件:new_on.h和new_off.h这些头文件将定义前面创建的new/delete宏取消他们的定义。采用这种方法的优点在于:不强迫用户遵循特定#include顺序,因此更灵活;同时避免了处理预编译的头文件的复杂性。
#include"new_off.h"
#include<map>
#include<string>
//这里包含STL或者其他头文件
#include"new_on.h"
#include"MemoryManager.h"
//new_on.h 文件
#define new new( __FILE__, __LINE__ )
#define delete (setOwner( __FILE__, __LINE__ ), false) ? setOwner( "", 0 ) : delete
#define malloc(sz) AllocateMemory( __FILE__, __LINE__, sz, MM_MALLOC )
#define calloc(num, sz) AllocateMemory( __FILE__, __LINE__, sz*num, MM_CALLOC )
#define realloc(ptr, sz) AllocateMemory( __FILE__, __LINE__, sz, MM_REALLOC, ptr )
#define free(sz) deAllocateMemory( __FILE__, __LINE__, sz, MM_FREE )
//new_off.h 文件
#undef new
#undef delete
#undef malloc
#undef calloc
#undef realloc
#undef free
// MemoryManager.h 文件
#pragma once
/***
* File: MemoryManager.h - Header File
* -----------------------------------------------------
* Author: Peter Dalton
* Date: 3/23/01 1:15:27 PM
*
* Description:
This Memory Manager software provides the following functionality:
1. Seamless interface.
2. Tracking all memory allocations and deallocations.
3. Reporting memory leaks, unallocated memory.
4. Reporting memory bounds violations.
5. Reporting the percentage of allocated memory that is actually being used.
6. Customizable tracking.
The code is self documented, thus reading through this header file should tell you
everything that you would ever need to know inorder to use the memory manager.
*
* Copyright (C) Peter Dalton, 2001.
* All rights reserved worldwide.
*
* This software is provided "as is" without express or implied warranties. You may freely copy
* and compile this source into applications you distribute provided that the copyright text
* below is included in the resulting source code, for example:
* "Portions Copyright (C) Peter Dalton, 2001"
*/
#ifndef _MEMORYMANAGER_H__
#define _MEMORYMANAGER_H__
#ifdef _DEBUG
#define ACTIVATE_MEMORY_MANAGER
#endif
#define ACTIVATE_MEMORY_MANAGER
#include "new_off.h" // Make sure that the new/delete are not declared to avoid
// circular definitions.
#include <stdlib.h> // Required for malloc() and free()
// Only activate the memory manager if the flag has been defined. This allows for the
// performance hit to be avoided if desired.
#ifdef ACTIVATE_MEMORY_MANAGER
/*******************************************************************************************/
// ***** User interface, these methods can be used to set parameters within the Memory
// ***** Manager to control the type and extent of the memory tests that are performed. Note
// ***** that it is not necessary to call any of these methods, you will get the default
// ***** Memory Manager automatically.
void dumpLogReport( void );
/* dumpLogReport():
* Dump the log report to the file, this is the same method that is automatically called
* upon the programs termination to report all statistical information.
*/
void dumpMemoryAllocations( void );
/* dumpMemoryAllocations():
* Report all allocated memory to the log file.
*/
void setLogFile( char *file );
/* setLogFile():
* Allows for the log file to be changed from the default.
*/
void setExhaustiveTesting( bool test = true );
/* setExhaustiveTesting():
* This method allows for exhaustive testing. It has the same functionality as the following
* function calls => setLogAlways( true ); setPaddingSize( 1024 );
*/
void setLogAlways( bool log = true );
/* setLogAlways():
* Sets the flag for exhaustive information logging. All information is sent to the log file.
*/
void setPaddingSize( int size = 4 );
/* setPaddingSize():
* Sets the padding size for memory bounds checks.
*/
void cleanLogFile( bool clean = true );
/* cleanLogFile():
* Cleans out the log file by deleting it.
*/
void breakOnAllocation( int allocationCount );
/* breakOnAllocation():
* Allows you to set a break point on the n-th allocation.
*/
void breakOnDeallocation( void *address );
/* breakOnDeallocation():
* Sets a flag that will set a break point when the specified memory is deallocated.
*/
void breakOnReallocation( void *address );
/* breakOnReallocation():
* Sets a flag that will set a break point when the specified memory is reallocated by
* using the realloc() method.
*/
/*******************************************************************************************/
// ***** Memory Manager specific definitions. Below are the definitions that make up the
// ***** Memory Manager.
// Posible allocation/deallocation types.
typedef char ALLOC_TYPE;
const ALLOC_TYPE MM_UNKNOWN = 0; // Declared as characters to minimize memory footprint,
const ALLOC_TYPE MM_NEW = 1; // char = 1 byte
const ALLOC_TYPE MM_NEW_ARRAY = 2; // enum types = int = 32 bits = 8 bytes on standard machines
const ALLOC_TYPE MM_MALLOC = 3;
const ALLOC_TYPE MM_CALLOC = 4;
const ALLOC_TYPE MM_REALLOC = 5;
const ALLOC_TYPE MM_DELETE = 6;
const ALLOC_TYPE MM_DELETE_ARRAY = 7;
const ALLOC_TYPE MM_FREE = 8;
void *AllocateMemory( const char *file, int line, size_t size, ALLOC_TYPE type, void *address = NULL );
/* AllocateMemory():
* This is the main memory allocation routine, this is called by all of the other
* memory allocation routines to allocate and track memory.
*/
void deAllocateMemory( void *address, ALLOC_TYPE type );
/* deAllocateMemory():
* This is the main memory deallocation routine. This method is used by all of the
* other de-allocation routines for de-allocating and tracking memory.
*/
void setOwner( const char *file, int line );
/* setOwner():
* This method is used by the deallocation methods to record the source file and line
* number that is requesting the allocation. Note that it is important to create a
* seperate method for deallocation since we can not pass the addition parameters to
* the delete methods like we do with the new methods.
*/
/*******************************************************************************************/
// ***** Here we define a static class that will be responsible for initializing the Memory
// ***** Manager. It is critical that it is placed here within the header file to ensure
// ***** that this static object will be created before any other static objects are
// ***** intialized. This will ensure that the Memory Manager is alive when other static
// ***** objects allocate and deallocate memory. Note that static objects are deallocated
// ***** in the reverse order in which they are allocated, thus this class will be
// ***** deallocated last.
class Initialize { public: Initialize(); };
static Initialize InitMemoryManager;
/*******************************************************************************************/
// ***** Global overloaded new/delete operators
// ***** These two routines should never get called, however they are provided here for
// ***** clarity and to through.
inline void* operator new( size_t size ) { return malloc( size ); }
inline void* operator new[]( size_t size ) { return malloc( size ); }
/**
* operator new():
* Here is the overloaded new operator, responsible for allocating and tracking the requested
* memory.
*
* Return Type: void* -> A pointer to the requested memory.
* Arguments:
* size_t size : The size of memory requested in BYTES
* const char *file : The file responsible for requesting the allocation.
* int line : The line number within the file requesting the allocation.
*/
inline void* operator new( size_t size, const char *file, int line )
{
return AllocateMemory( file, line, size, MM_NEW );
}
/*******************************************************************************************/
/**
* operator new[]():
* Here is the overloaded new[] operator, responsible for allocating and tracking the
* requested memory.
*
* Return Type: void* -> A pointer to the requested memory.
* Arguments:
* size_t size : The size of memory requested in BYTES
* const char *file : The file responsible for requesting the allocation.
* int line : The line number within the file requesting the allocation.
*/
inline void* operator new[]( size_t size, const char *file, int line )
{
return AllocateMemory( file, line, size, MM_NEW_ARRAY );
}
/*******************************************************************************************/
/**
* operator delete():
* This routine is responsible for de-allocating the requested memory.
*
* Return Type: void
* Arguments:
* void *address : A pointer to the memory to be de-allocated.
*/
inline void operator delete( void *address )
{
if (!address) return; // ANSI states that delete will allow NULL pointers.
deAllocateMemory( address, MM_DELETE );
}
/*******************************************************************************************/
/**
* operator delete[]():
* This routine is responsible for de-allocating the requested memory.
*
* Return Type: void
* Arguments:
* void *address : A pointer to the memory to be de-allocated.
*/
inline void operator delete[]( void *address )
{
if (!address) return; // ANSI states that delete will allow NULL pointers.
deAllocateMemory( address, MM_DELETE_ARRAY );
}
// ***** These two routines should never get called, unless an error occures during the
// ***** allocation process. These need to be defined to make Visual C++ 6.0 happy.
// ***** If there was an allocation problem these method would be called automatically by
// ***** the operating system. C/C++ Users Journal (Vol. 19 No. 4 -> April 2001 pg. 60)
// ***** has an excellent explanation of what is going on here.
inline void operator delete( void *address, const char *file, int line ) { free( address ); }
inline void operator delete[]( void *address, const char *file, int line ) { free( address ); }
/*******************************************************************************************/
// These #defines are the core of the memory manager. This overrides standard memory
// allocation and de-allocation routines and replaces them with the memory manager's versions.
// This allows for memory tracking and statistics to be generated. These #defines are
// included in the new_on.h header so that they are listed only once.
#include "new_on.h"
#endif /* ACTIVATE_MEMORY_MANAGER */
#endif /* _MEMORYMANAGER_H__ */
// ***** End of MemoryManager.h
/*******************************************************************************************/
/*******************************************************************************************/
//MemoryManager.cpp 文件
#include "MemoryManager.h"
/***
* File: MemoryManager.cpp - Implements MemoryManager.h
* -----------------------------------------------------
* Author: Peter Dalton
* Date: 3/23/01 1:23:45 PM
*
* Description:
This Memory Manager software provides the following functionality:
1. Seamless interface.
2. Tracking all memory allocations and deallocations.
3. Reporting memory leaks, unallocated memory.
4. Reporting memory bounds violations.
5. Reporting the percentage of allocated memory that is actually being used.
6. Customizable tracking.
The code is self documented, thus reading through this implementation file should
explain how everything is implemented and the reasoning behind it.
*
* Copyright (C) Peter Dalton, 2001.
* All rights reserved worldwide.
*
* This software is provided "as is" without express or implied warranties. You may freely copy
* and compile this source into applications you distribute provided that the copyright text
* below is included in the resulting source code, for example:
* "Portions Copyright (C) Peter Dalton, 2001"
*/
#include <string.h> // It is important to note here the order in which the files are
#include <stdarg.h> // included to avoid syntax and linking errors. First you should
#include <stdio.h> // include all of the required standard header files followed by
#include <time.h> // the MemoryManager.h header. All other custom file should be
#include <assert.h> // included after the MemoryManager.h header.
#include "MemoryManager.h"
/*******************************************************************************************/
/*******************************************************************************************/
// ***** Implementation:
#ifdef ACTIVATE_MEMORY_MANAGER // Only execute if the memory manager has been enabled.
/*******************************************************************************************/
// Turn off the defined macros to avoid confusion. We want to avoid circular definition,
// it is also not desired to track memory allocations within the memory manager module.
#include "new_off.h"
// Define our own version of assert to simply set a break point.
#define m_assert(x) if ((x) == false) __asm { int 3 } // Set a break point
/*******************************************************************************************/
// ***** Global Variables Definitions:
const long PADDING = 0xDEADC0DE;
const long BODY = 0xBAADC0DE;
const char BREAK_ON_DEALLOC = 0x1;
const char BREAK_ON_REALLOC = 0x2;
const int HASH_SIZE = 1024;
int NumAllocations = 0;
char LOGFILE[40] = "memoryLogFile.log";
const char* const s_allocationTypes[] = { "Unknown", "new", "new[]", "malloc", "calloc",
"realloc", "delete", "delete[]", "free" };
/*******************************************************************************************/
// ***** Here are the containers that make up the memory manager.
struct StackNode { // This struct is used to hold the file name and line
const char *fileName; // number of the file that is requesting a deallocation.
unsigned short lineNumber; // Only deallocations are recorded since the allocation
StackNode *next; // routines accept these additional parameters.
};
struct MemoryNode // This struct defines the primary container for tracking
{ // all memory allocations. It holds information that
size_t actualSize; // will be used to track memory violations and information
size_t reportedSize; // to help the user track down specific problems reported
void *actualAddress; // to the log file upon termination of the program.
void *reportedAddress;
char sourceFile[30]; // I have tried to keep the physical size of this struct
unsigned short sourceLine; // to a minimum, to reduce the memory tracking overhead.
unsigned short paddingSize; // At the same time I have tried to allow for as much
char options; // flexibility and information holding as possible.
long predefinedBody;
ALLOC_TYPE allocationType;
MemoryNode *next, *prev;
void InitializeMemory( long body = BODY ) ; // Initailize the nodes memory for interrogation.
};
// This class implements a basic stack for record keeping. It is necessary to use this class
// instead of the STL class since we need to explicitly call the init() method to initialize
// the data members. This is due to the restriction of only using the malloc() method within
// this file to avoid calling our own new() method and creating circular definitions. It is
// necessary to create this stack for logging file information when deallocating memory due to
// to order in which memory is deallocated and the effect of the delete macro. To understand
// this better refer to the sample illustration below.
//
// Sample Code file1 => delete classAObject;
// file2 => ~classA() { delete[] intArray; }
//
// Function Calls 1. setOwner( file1, line );
// 2. setOwner( file2, line );
// 3. deAllocate( intArray, MM_DELETE_ARRAY );
// 4. deAllocate( classAObject, MM_DELETE );
//
// The order of operations requires a stack for proper file tracking.
class myStack
{
public:
myStack() {}
__inline void init()
{ m_head = NULL; m_count = 0; }
__inline bool empty()
{ return (m_count == 0); }
__inline StackNode* top()
{ return m_head; }
__inline void push( StackNode *n )
{ if (!n) return; n->next = m_head; m_head = n; m_count++; }
__inline StackNode* pop()
{ StackNode *n = m_head; if (n) m_head = m_head->next; m_count--; return n; }
private:
StackNode *m_head;
int m_count;
};
// This class provides the implementation for the Memory Manager. I created this class to
// act as a container to centeralize the control instead of allowing everyone to become
// intertangled. Be design I have also left a lot of data members public for ease of access
// since this file is the only one that can ever create a MemoryManager object.
class MemoryManager
{
public:
MemoryManager( void ) {}; // Default Constructor.
~MemoryManager( void ) {}; // Destructor.
void initialize( void ); // Initailize internal memory.
void release( void ); // Release internal memory.
// Hash Table Routines
void insertMemoryNode( MemoryNode *node ); // Insert a new memory node.
MemoryNode *getMemoryNode( void *address ); // Retrieve a memory node.
MemoryNode *removeMemoryNode( void *address ); // Remove a memory node.
bool validateMemoryUnit( MemoryNode *node ); // Validate a memory node's memory.
// Memory Caching to minimize allocations within the memory manager
void deallocateMemory( MemoryNode *node );
MemoryNode* allocateMemory( void );
// Error Reporting Routines
void dumpLogReport( void );
void dumpMemoryAllocations( void );
void log( char *s, ... );
// User programmable options
int m_breakOnAllocationCount;
unsigned int m_paddingSize;
bool m_logAlways;
bool m_cleanLogFileOnFirstRun;
// Statistical Information
int m_totalMemoryAllocations; // The number of allocations.
unsigned int m_totalMemoryAllocated; // Number of bytes allocated.
unsigned int m_totalMemoryUsed; // Number of bytes used.
unsigned int m_peakMemoryAllocation; // The largest memory allocation.
unsigned int m_peakTotalNumAllocations; // The largest number of allocation existing simaltaniously.
unsigned int m_overheadMemoryCost; // The current amount of memory required for memory tracking.
unsigned int m_peakOverHeadMemoryCost; // The peak overhead memory cost.
unsigned int m_totalOverHeadMemoryCost; // The total overhead memory cost.
unsigned int m_allocatedMemory; // The current amount of allocated memory.
unsigned int m_numBoundsViolations; // The number of memory bounds violations.
// Stack for tracking file information for deallocations.
myStack m_topStack;
unsigned int m_numAllocations; // The number of entries within the hash table.
private:
int getHashIndex( void *address ); // Given an address this returns the hash table index
int calculateUnAllocatedMemory(); // Return the amount of unallocated memory.
MemoryNode *m_hashTable[HASH_SIZE]; // Hash Table container for tracking memory allocations.
MemoryNode *m_memoryCache; // Used for caching unused memory nodes.
};
MemoryManager *s_manager = NULL; // Declaration of the one and only Memory Manager Object
/*******************************************************************************************/
// ***** Function Prototypes: Refer to implemations below for additional details.
bool InitializeMemoryManager( void );
void releaseMemoryManager( void );
char *formatOwnerString( const char *file, int line );
char *sourceFileStripper( const char *sourceFile );
void log( char *s, ... );
char *insertCommas( unsigned long value );
char *memorySizeString( unsigned long size, bool lengthenString = true );
/*******************************************************************************************/
/**
* AllocateMemory():
* This is the main memory allocation routine, this is called by all of the other
* memory allocation routines to allocate and track memory.
*
* Return Type: void
* Arguments:
* const char *file : The file requesting the deallocation.
* int line : The line within the file requesting the deallocation.
* size_t size : The size of the memory to be reallocated.
* ALLOC_TYPE type : The type of reallocation being performed.
*/
void *AllocateMemory( const char *file, int line, size_t size, ALLOC_TYPE type, void *address )
{
MemoryNode *memory;
// If the memory manager has not yet been initialized due to the order in which static
// variables are allocated, create the memory manager here.
if (!s_manager && !InitializeMemoryManager()) {
if (NumAllocations != 0) {
log( "The Memory Manager has already been released from memory, however an allocation was requested" );
log( "\t%-40s", formatOwnerString( file, line ) );
}
return malloc( size ); // Try to allocate the memory for the requesting process.
}
if (size == 0) size = 1; // ANSI states that allocation requests of size 0 should return
// a valid pointer.
// Has the user requested to break on the N-th allocation.
m_assert( s_manager->m_totalMemoryAllocations != s_manager->m_breakOnAllocationCount );
// If the type is UNKNOWN then this allocation was made from a source not set up to
// use memory tracking, include the MemoryManager header within the source to elimate
// this error.
m_assert( type != MM_UNKNOWN );
if (type == MM_REALLOC) {
MemoryNode *memory = s_manager->removeMemoryNode( address );
// Validate that the memory exists
m_assert( memory );
if (!memory) {
s_manager->log( "Request to reallocate RAM that was never allocated." );
}
// Validate that there is not a allocation/reallocation mismatch
m_assert( memory->allocationType == MM_MALLOC ||
memory->allocationType == MM_CALLOC ||
memory->allocationType == MM_REALLOC );
// Validate that a break point on reallocation has not been requested.
m_assert( (memory->options & BREAK_ON_REALLOC) == 0x0 );
memory->actualSize = size + s_manager->m_paddingSize * sizeof(long)*2;
memory->reportedSize = size;
memory->actualAddress = realloc( memory->actualAddress, memory->actualSize );
}
else {
// Create a new memory block for tracking the memory
memory = s_manager->allocateMemory();
// Validate the memory node allocation
m_assert( memory != NULL );
if (memory == NULL) {
s_manager->log( "Could not allocate memory for memory tracking. Out of memory." );
}
memory->actualSize = size + s_manager->m_paddingSize * sizeof(long)*2;
memory->reportedSize = size;
memory->actualAddress = malloc( memory->actualSize );
memory->options = 0;
}
memory->reportedAddress = (char*)memory->actualAddress + s_manager->m_paddingSize * sizeof(long);
memory->sourceLine = line;
memory->paddingSize = s_manager->m_paddingSize;
memory->allocationType = type;
strcpy( memory->sourceFile, sourceFileStripper( file ) );
if (s_manager->m_logAlways) {
s_manager->log( "Memory Allocation : %-40s %8s(0x%08p) : %s", formatOwnerString( file, line ),
s_allocationTypes[type], memory->reportedAddress, memorySizeString( size ) );
}
// Validate the memory allocated
m_assert( memory->actualAddress );
if (!memory->actualAddress) {
s_manager->log( "Request for allocation failed. Out of memory." );
}
// Initialize the memory allocated for tracking upon deallocation
if (type == MM_CALLOC) memory->InitializeMemory( 0x00000000 );
else memory->InitializeMemory( 0xBAADC0DE );
// Insert the memory node into the hash table, this is a linked list hash table.
s_manager->insertMemoryNode( memory );
return memory->reportedAddress;
}
/*******************************************************************************************/
/**
* deAllocateMemory():
* This is the main memory de-allocation routine. This method is used by all of the
* other de-allocation routines for de-allocating and tracking memory.
*
* Return Type: void
* Arguments:
* void *address : The address of memory to be deallocated.
* ALLOC_TYPE type : The type of deallocation being performed.
*/
void deAllocateMemory( void *address, ALLOC_TYPE type )
{
// If the memory manager has not yet been initialized due to the order in which static
// variables are allocated, create the memory manager here.
if (!s_manager && !InitializeMemoryManager()) {
free( address ); // Release the memory
if (NumAllocations != 0) {
log( "The Memory Manager has already been released from memory, however a deallocation was requested" );
}
return;
}
// The topStack contains the logged information, such as file name and line number.
StackNode *info = s_manager->m_topStack.empty() ? NULL : s_manager->m_topStack.top();
// Does the memory node exist within the hash table of the memory manager.
MemoryNode *memory = s_manager->removeMemoryNode( address );
if (!memory) { // Validate that the memory was previously allocated. If the memory was not logged
free( address ); // by the memory manager simple free the memory and return. We do not log or
return; // create any errors since we want the memory manager to be as seemless as possible.
}
// Log the memory deallocation if desired.
if (s_manager->m_logAlways) {
s_manager->log( "Memory Deallocation : %-40s %8s(0x%08p) : %s",
formatOwnerString( info->fileName, info->lineNumber ),
s_allocationTypes[type], address, memorySizeString( memory->reportedSize ) );
}
// If the type is UNKNOWN then this allocation was made from a source not set up to
// use memory tracking, include the MemoryManager header within the source to elimate
// this error.
m_assert( type != MM_UNKNOWN );
// Validate that no memory errors occured. If any errors have occured they will be written to the log
// file by the validateMemoryUnit() method.
s_manager->validateMemoryUnit( memory );
// Validate that there is not a allocation/deallocation mismatch
m_assert( type == MM_DELETE && memory->allocationType == MM_NEW ||
type == MM_DELETE_ARRAY && memory->allocationType == MM_NEW_ARRAY ||
type == MM_FREE && memory->allocationType == MM_MALLOC ||
type == MM_FREE && memory->allocationType == MM_CALLOC ||
type == MM_FREE && memory->allocationType == MM_REALLOC );
// Validate that a break on deallocate was not set
m_assert( (memory->options & BREAK_ON_DEALLOC) == 0x0 );
// Free the memory
free( memory->actualAddress );
// Free the memory used to create the Memory Node
s_manager->deallocateMemory( memory );
// Free the info node used to hold the file and line number information for this deallocation.
if (info) {
s_manager->m_topStack.pop();
free( info );
}
}
/*******************************************************************************************/
/*******************************************************************************************/
// ***** Helper Functions
/*******************************************************************************************/
/*******************************************************************************************/
// ****** Implementation of the MemoryManager Class:
/**
* MemoryManager::initialize():
* This method is responsible for initializing the Memory Manager.
*
* Return Type : void
* Arguments : NONE
*/
void MemoryManager::initialize( void )
{
m_breakOnAllocationCount = -1;
m_paddingSize = 4;
m_logAlways = true;
m_cleanLogFileOnFirstRun = true;
m_totalMemoryAllocated = m_totalMemoryUsed = m_totalMemoryAllocations = 0;
m_peakMemoryAllocation = m_numAllocations = m_peakTotalNumAllocations = 0;
m_overheadMemoryCost = m_totalOverHeadMemoryCost = m_peakOverHeadMemoryCost = 0;
m_allocatedMemory = m_numBoundsViolations = 0;
for (int ii = 0; ii < HASH_SIZE; ++ii) {
m_hashTable[ii] = NULL;
}
m_topStack.init();
m_memoryCache = NULL;
}
/*******************************************************************************************/
/**
* MemoryManager::release():
* This method is responsible for releasing the Memory Manager. It dumps the log file and
* cleans up any memory that has been left behind.
*
* Return Type : void
* Arguments : NONE
*/
void MemoryManager::release( void )
{
dumpLogReport(); // Dump the statistical information to the log file.
// If there are memory leaks, be sure to clean up memory that the memory manager allocated.
// It would really look bad if the memory manager created memory leaks!!!
if (m_numAllocations != 0) {
for (int ii = 0; ii < HASH_SIZE; ++ii) {
while (m_hashTable[ii]) {
MemoryNode *ptr = m_hashTable[ii];
m_hashTable[ii] = m_hashTable[ii]->next;
free( ptr->actualAddress ); // Free the memory left behind by the memory leak.
free( ptr ); // Free the memory used to create the Memory Node.
}
}
}
// Clean up the stack if it contains entries.
while (!m_topStack.empty()) {
free( m_topStack.top() );
m_topStack.pop();
}
// Clean the memory cache
MemoryNode *ptr;
while (m_memoryCache) {
ptr = m_memoryCache;
m_memoryCache = ptr->next;
free( ptr );
}
}
/*******************************************************************************************/
/**
* MemoryManager::insertMemoryNode():
* Inserts a memory node into the hash table and collects statistical information.
*
* Return Type : void
* Arguments :
* MemoryNode *node : The memory node to be inserted into the hash table.
*/
void MemoryManager::insertMemoryNode( MemoryNode *node )
{
int hashIndex = getHashIndex( node->reportedAddress );
node->next = m_hashTable[hashIndex];
node->prev = NULL;
if (m_hashTable[hashIndex]) m_hashTable[hashIndex]->prev = node;
m_hashTable[hashIndex] = node;
// Collect Statistic Information.
m_numAllocations++;
m_allocatedMemory += node->reportedSize;
if (m_allocatedMemory > m_peakMemoryAllocation) m_peakMemoryAllocation = m_allocatedMemory;
if (m_numAllocations > m_peakTotalNumAllocations) m_peakTotalNumAllocations = m_numAllocations;
m_totalMemoryAllocated += node->reportedSize;
m_totalMemoryAllocations++;
}
/*******************************************************************************************/
/**
* MemoryManager::getMemoryNode():
* Returns the memory node for the given memory address, if the node does not exist a
* NULL pointer is returned.
*
* Return Type : MemoryNode* -> A pointer to the requested memory node.
* Arguments :
* void *address : The address of the memory to be retrieved.
*/
MemoryNode* MemoryManager::getMemoryNode( void *address )
{
MemoryNode *ptr = m_hashTable[getHashIndex( address )];
while (ptr && ptr->reportedAddress != address) {
ptr = ptr->next;
}
return ptr;
}
/*******************************************************************************************/
/**
* MemoryManager::removeMemoryNode():
* Returns the memory node for the given memory address, if the node does not exist, a NULL
* pointer is returned. This method also removes the memory node from the hash table.
*
* Return Type : MemoryNode* -> A pointer to the requested memory node.
* Arguments :
* void *address : The address of the memory to be retrieved.
*/
MemoryNode* MemoryManager::removeMemoryNode( void *address )
{
int hashIndex = getHashIndex( address );
if (hashIndex == 17)
int ttt = 0;
MemoryNode *ptr = m_hashTable[hashIndex];
while (ptr && ptr->reportedAddress != address) {
ptr = ptr->next;
}
if (ptr) {
if (ptr->next) ptr->next->prev = ptr->prev;
if (ptr->prev) ptr->prev->next = ptr->next;
else m_hashTable[hashIndex] = ptr->next;
// Update Statistical Information.
m_numAllocations--;
m_allocatedMemory -= ptr->reportedSize;
}
return ptr;
}
/*******************************************************************************************/
/**
* MemoryManager::validateMemoryUnit():
* Given a Memory Node, this method will interrogate its memory looking for bounds violations
* and the number of bytes that were actually used. This method should only be called before
* deleting a Memory Node to generate statistical information. This method will report all
* errors to the log file. Returns TRUE if no bounds violations where found, otherwise FALSE.
*
* Return Type : bool -> True if no bounds violations, otherwise False.
* Arguments :
* MemoryNode *node : The memory node to be interrogated.
*/
bool MemoryManager::validateMemoryUnit( MemoryNode *node )
{
bool success = true;
unsigned int ii;
unsigned int totalBytesUsed = 0, boundViolations = 0;
// Detect bounds violations
long *beginning = (long*)node->actualAddress;
long *ending = (long*)((char*)node->actualAddress + node->actualSize - node->paddingSize*sizeof(long));
for (ii = 0; ii < node->paddingSize; ++ii) {
if (beginning[ii] != PADDING || ending[ii]!= PADDING) {
success = false; // Report the bounds violation.
boundViolations++;
}
}
if (!success) m_numBoundsViolations++;
// Attempt to determine how much of the allocated memory was actually used.
// Initialize the memory padding for detecting bounds violations.
long *lptr = (long*)node->reportedAddress;
unsigned int len = node->reportedSize / sizeof(long);
unsigned int cnt;
for (cnt = 0; cnt < len; ++cnt) {
if (lptr[cnt] != node->predefinedBody) totalBytesUsed += sizeof(long);
}
char *cptr = (char*)(&lptr[cnt]);
len = node->reportedSize - len*sizeof(long);
for (cnt = 0; cnt < len; ++cnt) {
if (cptr[cnt] != (char)node->predefinedBody) totalBytesUsed++;
}
m_totalMemoryUsed += totalBytesUsed;
if (m_logAlways && totalBytesUsed != node->reportedSize) { // Report the percentage
this->log( "Unused Memory Detected : %-40s %8s(0x%08p) : %s", // of waisted memory space.
formatOwnerString( node->sourceFile, node->sourceLine ),
s_allocationTypes[node->allocationType], node->reportedAddress,
memorySizeString( node->reportedSize - totalBytesUsed ) );
}
if (m_logAlways && !success) { // Report the memory
this->log( "Bounds Violation Detected: %-40s %8s(0x%08p)", // bounds violation.
formatOwnerString( node->sourceFile, node->sourceLine ),
s_allocationTypes[node->allocationType], node->reportedAddress );
}
return success;
}
/*******************************************************************************************/
/**
* MemoryManager::deallocateMemory():
* This method adds the MemoryNode to the memory cache for latter use.
*
* Return Type : void
* Arguments :
* MemoryNode *node : The MemoryNode to be released.
*/
void MemoryManager::deallocateMemory( MemoryNode *node )
{
m_overheadMemoryCost -= (node->paddingSize * 2 * sizeof(long));
node->next = m_memoryCache;
m_memoryCache = node;
}
/*******************************************************************************************/
/**
* MemoryManager::allocateMemory():
* This method checks the memory cache for unused MemoryNodes, if one exists it is removed
* from the cache and returned. Otherwise, new memory is allocated for the MemoryNode and
* returned.
*
* Return Type : MemoryNode* -> The allocated MemoryNode.
* Arguments : NOEN
*/
MemoryNode* MemoryManager::allocateMemory( void )
{
if (!m_memoryCache) {
int overhead = m_paddingSize * 2 * sizeof(long) + sizeof( MemoryNode );
m_overheadMemoryCost += overhead;
m_totalOverHeadMemoryCost += overhead;
if (m_overheadMemoryCost > m_peakOverHeadMemoryCost) {
m_peakOverHeadMemoryCost = m_overheadMemoryCost;
}
return (MemoryNode*)malloc( sizeof(MemoryNode) );
}
else {
int overhead = m_paddingSize * 2 * sizeof(long);
m_overheadMemoryCost += overhead;
m_totalOverHeadMemoryCost += overhead;
if (m_overheadMemoryCost > m_peakOverHeadMemoryCost) {
m_peakOverHeadMemoryCost = m_overheadMemoryCost;
}
MemoryNode *ptr = m_memoryCache;
m_memoryCache = m_memoryCache->next;
return ptr;
}
}
/*******************************************************************************************/
/**
* MemoryManager::dumpLogReport():
* This method implements the main reporting system. It reports all of the statistical
* information to the desired log file.
*
* Return Type : void
* Arguments : NONE
*/
void MemoryManager::dumpLogReport( void )
{
if (m_cleanLogFileOnFirstRun) { // Cleanup the log?
unlink( LOGFILE ); // Delete the existing log file.
m_cleanLogFileOnFirstRun = false; // Toggle the flag.
}
FILE *fp = fopen( LOGFILE, "ab" ); // Open the log file
if (!fp) return;
time_t t = time( NULL );
tm *time = localtime( &t );
int memoryLeak = calculateUnAllocatedMemory();
int totalMemoryDivider = m_totalMemoryAllocated != 0 ? m_totalMemoryAllocated : 1;
// Header Information
fprintf( fp, "\r\n" );
fprintf( fp, "******************************************************************************* \r\n");
fprintf( fp, "********* Memory report for: %02d/%02d/%04d %02d:%02d:%02d ********* \r\n", time->tm_mon + 1, time->tm_mday, time->tm_year + 1900, time->tm_hour, time->tm_min, time->tm_sec );
fprintf( fp, "******************************************************************************* \r\n");
fprintf( fp, "\r\n" );
// Report summary
fprintf( fp, " T O T A L M E M O R Y U S A G E \r\n" );
fprintf( fp, "------------------------------------------------------------------------------- \r\n" );
fprintf( fp, " Total Number of Dynamic Allocations: %10s\r\n", insertCommas( m_totalMemoryAllocations ) );
fprintf( fp, " Reported Memory usage to the Application: %s\r\n", memorySizeString( m_totalMemoryAllocated ) );
fprintf( fp, " Actual Memory use by the Application: %s\r\n", memorySizeString( m_totalOverHeadMemoryCost + m_totalMemoryAllocated ) );
fprintf( fp, " Memory Tracking Overhead: %s\r\n", memorySizeString( m_totalOverHeadMemoryCost ) );
fprintf( fp, "\r\n");
fprintf( fp, " P E A K M E M O R Y U S A G E \r\n");
fprintf( fp, "------------------------------------------------------------------------------- \r\n");
fprintf( fp, " Peak Number of Dynamic Allocations: %10s\r\n", insertCommas( m_peakTotalNumAllocations ) );
fprintf( fp, " Peak Reported Memory usage to the application: %s\r\n", memorySizeString( m_peakMemoryAllocation ) );
fprintf( fp, " Peak Actual Memory use by the Application: %s\r\n", memorySizeString( m_peakOverHeadMemoryCost + m_peakMemoryAllocation ) );
fprintf( fp, " Peak Memory Tracking Overhead: %s\r\n", memorySizeString( m_peakOverHeadMemoryCost ) );
fprintf( fp, "\r\n");
fprintf( fp, " U N U S E D M E M O R Y \r\n");
fprintf( fp, "------------------------------------------------------------------------------- \r\n");
fprintf( fp, " Percentage of Allocated Memory Actually Used: %10.2f %%\r\n", (float)(1 - (m_totalMemoryAllocated - m_totalMemoryUsed)/(float)totalMemoryDivider) * 100 );
fprintf( fp, " Percentage of Allocated Memory Not Used: %10.2f %%\r\n", (float)(m_totalMemoryAllocated - m_totalMemoryUsed)/(float)totalMemoryDivider * 100 );
fprintf( fp, " Memory Allocated but not Actually Used: %s\r\n", memorySizeString( m_totalMemoryAllocated - m_totalMemoryUsed ) );
fprintf( fp, "\r\n");
fprintf( fp, " B O U N D S V I O L A T I O N S \r\n");
fprintf( fp, "------------------------------------------------------------------------------- \r\n");
fprintf( fp, " Number of Memory Bounds Violations: %10s\r\n", insertCommas( m_numBoundsViolations ) );
fprintf( fp, "\r\n");
fprintf( fp, " M E M O R Y L E A K S \r\n");
fprintf( fp, "------------------------------------------------------------------------------- \r\n");
fprintf( fp, " Number of Memory Leaks: %10s\r\n", insertCommas( m_numAllocations ) );
fprintf( fp, " Amount of Memory Un-Allocated: %s\r\n", memorySizeString( memoryLeak ) );
fprintf( fp, " Percentage of Allocated Memory Un-Allocated: %10.2f %%\r\n", (float)(1 - (m_totalMemoryAllocated - memoryLeak)/(float)totalMemoryDivider) * 100 );
fprintf( fp, "\r\n");
if (m_numAllocations != 0) { // Are there memory leaks?
fclose( fp ); // Close the log file.
dumpMemoryAllocations(); // Display any memory leaks.
}
else {
fclose( fp );
}
}
/*******************************************************************************************/
/**
* MemoryManager::dumpMemoryAllocations():
* This method is responsible for reporting all memory that is currently allocated. This is
* achieved by reporting all memory that is still within the hash table.
*
* Return Type : void
* Arguments : NONE
*/
void MemoryManager::dumpMemoryAllocations( void )
{
if (m_cleanLogFileOnFirstRun) { // Cleanup the log?
unlink( LOGFILE ); // Delete the existing log file.
m_cleanLogFileOnFirstRun = false; // Toggle the flag.
}
FILE *fp = fopen( LOGFILE, "ab" ); // Open the log file
if (!fp) return;
fprintf( fp, " C U R R E N T L Y A L L O C A T E D M E M O R Y \r\n" );
fprintf( fp, "------------------------------------------------------------------------------- \r\n" );
for (int ii = 0, cnt = 1; ii < HASH_SIZE; ++ii) {
for (MemoryNode *ptr = m_hashTable[ii]; ptr; ptr = ptr->next) {
fprintf( fp, "** Allocation # %2d\r\n", cnt++ );
fprintf( fp, "Total Memory Size : %s\r\n", memorySizeString( ptr->reportedSize, false ) );
fprintf( fp, "Source File : %s\r\n", ptr->sourceFile );
fprintf( fp, "Source Line : %d\r\n", ptr->sourceLine );
fprintf( fp, "Allocation Type : %s\r\n", s_allocationTypes[ptr->allocationType] );
fprintf( fp, "\r\n");
}
}
fprintf( fp, "------------------------------------------------------------------------------- \r\n" );
fprintf( fp, "******************************************************************************* \r\n" );
fprintf( fp, "\r\n" );
fclose( fp );
}
/*******************************************************************************************/
/**
* MemoryManager::log():
* Dumps a specific string to the log file. Used for error reporting during runtime. This
* method accepts a variable argument lenght such as printf() for ease of reporting.
*
* Return Type : void
* Arguments :
* char *s : The string to be written to the log file.
* ... : The parameters to be placed within the string, simular to say: printf( s, ... )
*/
void MemoryManager::log( char *s, ... )
{
if (m_cleanLogFileOnFirstRun) { // Cleanup the log?
unlink( LOGFILE ); // Delete the existing log file.
m_cleanLogFileOnFirstRun = false; // Toggle the flag.
}
static char buffer[2048]; // Create the buffer
va_list list; // Replace the strings variable arguments with the provided
va_start( list, s ); // arguments.
vsprintf( buffer, s, list );
printf("%s\r\n", buffer);
va_end( list );
FILE *fp = fopen( LOGFILE, "ab" ); // Open the log file
if (!fp) return;
fprintf( fp, "%s\r\n", buffer ); // Write the data to the log file
fclose( fp ); // Close the file
}
/*******************************************************************************************/
/**
* MemoryManager::getHashIndex():
* Returns the hash index for the given memory address.
*
* Return Type : int -> The hash table index.
* Arguments :
* void *address : The address to determine the hash table index for.
*/
int MemoryManager::getHashIndex( void *address )
{
return ((unsigned int)address >> 4) & (HASH_SIZE -1);
}
/*******************************************************************************************/
/**
* MemoryManager::calculateUnAllocatedMemory():
* Returns the amount of unallocated memory in BYTES.
*
* Return Type : int -> The number of BYTES of unallocated memory.
* Arguments : NONE
*/
int MemoryManager::calculateUnAllocatedMemory( void )
{
int memory = 0;
for (int ii = 0; ii < HASH_SIZE; ++ii) {
for (MemoryNode *ptr = m_hashTable[ii]; ptr; ptr = ptr->next) {
memory += ptr->reportedSize;
}
}
return memory;
}
/*******************************************************************************************/
/*******************************************************************************************/
// ****** Implementation of the MemoryNode Struct
/**
* MemoryNode::InitializeMemory():
* Initialize the padding and the body of the allocated memory so that it can be interrogated
* upon deallocation.
*
* Return Type : void
* Arguments :
* long body : The value to which the body of the allocated memory should be intialized too.
*/
void MemoryNode::InitializeMemory( long body )
{
// Initialize the memory padding for detecting bounds violations.
long *beginning = (long*)actualAddress;
long *ending = (long*)((char*)actualAddress + actualSize - paddingSize*sizeof(long));
for (unsigned short ii = 0; ii < paddingSize; ++ii) {
beginning[ii] = ending[ii] = PADDING;
}
// Initialize the memory body for detecting unused memory.
beginning = (long*)reportedAddress;
unsigned int len = reportedSize / sizeof(long);
unsigned int cnt;
for (cnt = 0; cnt < len; ++cnt) { // Initialize the majority of the memory
beginning[cnt] = body;
}
char *cptr = (char*)(&beginning[cnt]);
len = reportedSize - len*sizeof(long);
for (cnt = 0; cnt < len; ++cnt) { // Initialize the remaining memory
cptr[cnt] = (char)body;
}
predefinedBody = body;
}
/*******************************************************************************************/
/*******************************************************************************************/
// ****** Implementation of the Initialize Class
/**
* Initialize::Initialize():
* Initialize the Memory Manager Object. This class is required to ensure that the Memory
* Manager has been created before dynamic allocation occure within other statically
* allocated objects.
*
* Return Type :
* Arguments : NONE
*/
Initialize::Initialize( void )
{
InitializeMemoryManager(); // Create the Memory Manager Object.
}
/*******************************************************************************************/
/*******************************************************************************************/
// ****** Implementation of Helper Functions
/**
* InitializeMemoryManager():
* This method is responsible for creating a Memory Manager Object. If the object already
* exists or is successfully created TRUE is returned. Otherwise if the object was
* previously created and has been destroyed FALSE is returned. The goal is to guarantee
* that the Memory Manager is the first object to be created and the last to be destroyed.
*
* Return Type : bool -> True if intialized, otherwise False.
* Arguments : NONE
*/
bool InitializeMemoryManager( void )
{
static bool hasBeenInitialized = false;
if (s_manager) { // The memory manager object already exists.
return true;
}
else if (hasBeenInitialized) { // The memory manager object has already been created
return false; // once, however it was release before everyone
} // was done.
else { // Create the memory manager object.
s_manager = (MemoryManager*)malloc( sizeof(MemoryManager) );
s_manager->initialize();
atexit( releaseMemoryManager ); // Log this function to be called upon program shut down.
hasBeenInitialized = true;
return true;
}
}
/*******************************************************************************************/
/**
* releaseMemoryManager():
* This method is automatically called when the application is terminated. It is important
* that this is the last function called to perform application cleanup since the memory
* manager object should be the last object to be destoryed, thus this must be the first
* method logged to perform application clean up.
*
* Return Type : void
* Arguments : NONE
*/
void releaseMemoryManager( void )
{
NumAllocations = s_manager->m_numAllocations;
s_manager->release(); // Dump the log report and free remaining memory.
free( s_manager );
s_manager = NULL;
}
/*******************************************************************************************/
/**
* formatOwnerString():
* This method is responsible for formating the owner string. This string states the file
* name and line number within the specified file.
*
* Return Type : char* -> A pointer to the string representing the owner string.
* Arguments :
* const char *file : The files name
* int line : The line number within the specified file.
*/
char *formatOwnerString( const char *file, int line )
{
static char str[90];
memset( str, 0, sizeof(str));
sprintf( str, "%s(%05d)", sourceFileStripper(file), line );
return str;
}
/*******************************************************************************************/
/**
* sourceFileStripper():
* This method takes a file name and strips off all directory information.
*
* Return Type : char* -> A pointer to the actual file minus all directory information.
* Arguments :
* const char *sourceFile : The file to strip.
*/
char *sourceFileStripper( const char *sourceFile )
{
if (!sourceFile) return NULL;
char *ptr = (char *)strrchr( sourceFile, '\\' );
if (ptr) return ptr + 1;
ptr = (char*)strrchr(sourceFile, '/');
if (ptr) return ptr + 1;
return (char*)sourceFile;
}
/*******************************************************************************************/
/**
* log():
* Dumps a specific string to the log file. Used for error reporting during runtime. This
* method accepts a variable argument lenght such as printf() for ease of reporting.
*
* Return Type : void
* Arguments :
* char *s : The string to be written to the log file.
* ... : The parameters to be placed within the string, simular to say: printf( s, ... )
*/
void log( char *s, ... )
{
static char buffer[2048]; // Create the buffer
va_list list;
va_start( list, s );
vsprintf( buffer, s, list );
va_end( list );
FILE *fp = fopen( LOGFILE, "ab" ); // Open the log file
if (!fp) return;
fprintf( fp, "%s\r\n", buffer ); // Write the data to the log file
fclose( fp ); // Close the file
}
/*******************************************************************************************/
/**
* insertCommas():
* This method takes a value and inserts commas, creating a nicely formated string. Thus
* the value => 23456 would be converted to the following string => 23,456.
*
* Return Type : char* -> A string representing the provided value with commas inserted.
* Arguments :
* unsigned long value : The value to insert commas into.
*/
char *insertCommas( unsigned long value )
{
static char str[30];
for (int ii = 0; ii < 30; ++ii) str[ii] = NULL;
sprintf(str, "%d", value);
if (strlen(str) > 3) {
memmove( &str[strlen(str)-3], &str[strlen(str)-4], 4 );
str[strlen(str) - 4] = ',';
}
if (strlen(str) > 7) {
memmove( &str[strlen(str)-7], &str[strlen(str)-8], 8 );
str[strlen(str) - 8] = ',';
}
if (strlen(str) > 11) {
memmove( &str[strlen(str)-11], &str[strlen(str)-12], 12 );
str[strlen(str) - 12] = ',';
}
return str;
}
/*******************************************************************************************/
/**
* memorySizeString():
* This method takes a memory size and creates a user friendly string that displays the
* memory size in bytes, K or M.
*
* Return Type : char* -> The final memory size string.
* Arguments :
* unsigned long size : The size of the memory.
* bool lengthenString : Whether or not to pad the string with white spaces.
*/
char *memorySizeString( unsigned long size, bool lengthenString /* = true */ )
{
static char str[90];
if (lengthenString) {
if (size > (1024*1024)) sprintf(str, "%10s (%7.2fM)", insertCommas(size), size / (1024.0 * 1024.0));
else if (size > 1024) sprintf(str, "%10s (%7.2fK)", insertCommas(size), size / 1024.0);
else sprintf(str, "%10s bytes ", insertCommas(size), size);
}
else {
if (size > (1024*1024)) sprintf(str, "%s (%7.2fM)", insertCommas(size), size / (1024.0 * 1024.0));
else if (size > 1024) sprintf(str, "%s (%7.2fK)", insertCommas(size), size / 1024.0);
else sprintf(str, "%s bytes ", insertCommas(size), size);
}
return str;
}
/*******************************************************************************************/
/*******************************************************************************************/
// ****** Implementation of Access Functions defined within MemoryManager.h
/**
* dumpLogReport():
* Dump the log report to the file, this is the same method that is automatically called
* upon the programs termination to report all statistical information.
*
* Return Type : void
* Arguments : NONE
*/
void dumpLogReport( void )
{
if (s_manager) s_manager->dumpLogReport();
}
/*******************************************************************************************/
/**
* dumpMemoryAllocations():
* Report all allocated memory to the log file.
*
* Return Type : void
* Arguments : NONE
*/
void dumpMemoryAllocations( void )
{
if (s_manager) s_manager->dumpMemoryAllocations();
}
/*******************************************************************************************/
/**
* setLogFile():
* Allows for the log file to be changed from the default.
*
* Return Type : void
* Arguments :
* char *file : The name of the new log file.
*/
void setLogFile( char *file )
{
if (file) strcpy( LOGFILE, file );
}
/*******************************************************************************************/
/**
* setExhaustiveTesting():
* This method allows for exhaustive testing. It has the same functionality as the following
* function calls => setLogAlways( true ); setPaddingSize( 1024 );
*
* Return Type : void
* Arguments :
* bool test : Whether or not to turn exhaustive testing on or off.
*/
void setExhaustiveTesting( bool test /* = true */ )
{
if (!s_manager) return;
if (test) {
setPaddingSize( 1024 );
setLogAlways();
}
else {
setPaddingSize();
setLogAlways( false );
}
}
/*******************************************************************************************/
/**
* setLogAlways():
* Sets the flag for exhaustive information logging. All information is sent to the log file.
*
* Return Type : void
* Arguments :
* bool log : Whether or not to log all information.
*/
void setLogAlways( bool log /* = true */ )
{
if (s_manager) s_manager->m_logAlways = log;
}
/*******************************************************************************************/
/**
* setPaddingSize():
* Sets the padding size for memory bounds checks.
*
* Return Type : void
* Arguments :
* int size : The new padding size.
*/
void setPaddingSize( int size /* = 4 */ )
{
if (s_manager && size > 0) s_manager->m_paddingSize = size;
}
/*******************************************************************************************/
/**
* cleanLogFile():
* Cleans out the log file by deleting it.
*
* Return Type : void
* Arguments :
* bool clean : Whether or not to clean the log file.
*/
void cleanLogFile( bool clean /* = true */ )
{
if (s_manager) s_manager->m_cleanLogFileOnFirstRun = true;
}
/*******************************************************************************************/
/**
* breakOnAllocation():
* Allows you to set a break point on the n-th allocation.
*
* Return Type : void
* Arguments :
* int allocationCount : The allocation count to break on.
*/
void breakOnAllocation( int allocationCount )
{
if (s_manager && allocationCount > 0) s_manager->m_breakOnAllocationCount = allocationCount;
}
/*******************************************************************************************/
/**
* breakOnDeallocation():
* Sets a flag that will set a break point when the specified memory is deallocated.
*
* Return Type : void
* Arguments :
* void *address : The address to break on when it is deallocated.
*/
void breakOnDeallocation( void *address )
{
if (!s_manager || !address) return;
MemoryNode *node = s_manager->getMemoryNode( address );
node->options |= BREAK_ON_DEALLOC;
}
/*******************************************************************************************/
/**
* breakOnReallocation():
* Sets a flag that will set a break point when the specified memory is reallocated by
* using the realloc() method.
*
* Return Type : void
* Arguments :
* void *address : The address to break on when it is reallocated.
*/
void breakOnReallocation( void *address )
{
if (!s_manager || !address) return;
MemoryNode *node = s_manager->getMemoryNode( address );
node->options |= BREAK_ON_REALLOC;
}
/*******************************************************************************************/
/**
* setOwner():
* This method is only called by the delete macro defined within the MemoryManager.h header.
* It is responsible for logging the file and line number for tracking information. For
* an explanation for the stack implementation refer to the MemoryManager class definition.
*
* Return Type : void
* Arguments :
* const char *file : The file requesting the deallocation.
* int line : The line number within the file.
*/
void setOwner( const char *file, int line )
{
if (s_manager) {
StackNode *n = (StackNode*)malloc( sizeof(StackNode) );
n->fileName = file;
n->lineNumber = line;
s_manager->m_topStack.push( n );
}
}
#endif /* ACTIVATE_MEMORY_MANAGER */
// ***** End of MemoryManager.cpp
/*******************************************************************************************/
/*******************************************************************************************/
文章介绍了一个内存管理器的设计,通过重载new和delete操作符以及使用宏定义来跟踪内存分配和释放。管理器记录每个内存块的文件和行号,检测内存泄漏、边界违规和未使用的内存。此外,它还提供了统计信息和自定义设置,如分配和释放时的日志记录、内存边界检查等。

434

被折叠的 条评论
为什么被折叠?



