|
Sculpture by Jeff Powell
|
|
Original artwork in stone, metal, and who knows what else
|
|
|
|
allocmem.c
/****************************************************************************
* allocmem.c
*
* A file of generic memory allocation handling routines.
*
* Users of this code should include allocmem.h, compile with one or both
* of DEBUG_ALLOCMEM and DEBUG_ALLOCMEM_DUMP defined, and compile and
* link in this file (allocmem.c).
*
* Doing so provides three functions that should be used to allocate,
* reallocate, and free all memory:
* void *AllocMem( int size );
* void *ReAllocMem( void *pOld, int size );
* void FreeMem( void *p );
*
* If you compile with DEBUG_ALLOCMEM defined, you'll get a ton of error
* checking done on memory allocation and free, with a summary report
* printed when the application terminates.
*
* If you additionally define DEBUG_ALLOCMEM_DUMP, you'll get a full dump
* of the contents of every allocated (and not free'd) block of memory
* that remains in the summary report printed when your program terminates.
*
* If you do not define DEBUG_ALLOCMEM, then these functions become
* simple wrappers around the typical C library memory allocation routines.
*
* Note: at one point, this program's output was sent to stderr. That
* was later changed, and it now goes to stdout. However, the old calls
* to fprintf() and other routines were never replaced (I was in a hurry
* at the time) so there are calls like:
* fprintf( stdout, yadda yadda yadda );
* in here. That's an artifact of the current state of the code. If/when
* I rewrite this code to handle the items in to to-do list below, I will
* modify it so that output can be directed to a particular destination
* without code modification and recompliation. Until then, the odd
* constructs that print to stdout will hang around.
*
* To do list:
*
* 1) Eliminate fixed buffer sizes. MAXFNAMES and MAXALLOCS are the big
* problems, along with all arrays based on those defines.
*
* 2) Write a one page summary of what this code does. (Yes, this
* comment block does tell you, but it would be nicer to have it
* written out in plain English.)
*
* 3) Add a way to get output sent to a selectable file handle without
* modifying the code. Perhaps another define, or perhaps a new
* routine entry to set the output destination.
*
* 4) Rework allocmem.h so that when allocmem is not compiled in, the
* macros become calls to malloc, realloc, and free, instead of the
* simplified wrapper functions in this file.
* -- or --
* Create a way to turn memory logging on and off via new API functions,
* and eliminate as much overhead as possible when logging is off.
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#ifdef DEBUG_ALLOCMEM
#include <limits.h>
#include "allocmem.h"
#define MAXFNAMES 50 /* max number of filenames we can store */
#define MAXALLOCS 10000 /* max number of malloc'd memory chunks */
/* we can handle at one time */
struct tag_allocinfo
{
int InUse; /* non-0 if this array element is in use */
int FnameIdx; /* ptr into array of file names */
int LineNo; /* line number this alloc was called from */
int AllocNum; /* the ordinal number of this alloc call */
void *p; /* the pointer returned by malloc */
int Size; /* the size of this allocation */
int ReallocCount; /* number of realloc calls made on this mem */
int PrevAlloc; /* previous allocation in the chain */
int NextAlloc; /* next allocation in the chain */
};
typedef struct tag_allocinfo allocinfo;
static int RegisteredExit = 0;
static char FileNames[MAXFNAMES][PATH_MAX] = { 0 };
static int OrdinalAllocationNumber = 0;
static int AllocCalls = 0;
static int ReAllocCalls = 0;
static int FreeCalls = 0;
static int AllocChain = -1;
static allocinfo AllocArray[ MAXALLOCS ] = { 0 };
static int ChainLen = 0;
static int MaxChainLen = 0;
static void LogReAllocMem( char *fname, int line, void *pold, void *pnew,
int newsize );
static void LogAllocMem( char *fname, int line, void *p, int size );
static void LogFreeMem( char *fname, int line, void *pold );
static int GetFileName( char *fname );
static void DoAllocSummary( void );
static void DumpBytes( unsigned char *p, int sz );
#endif
/****************************************************************************
* ALLOCMEM
*
* a function to allocate a chunk of 0-initialized memory
****************************************************************************/
void *
#ifndef DEBUG_ALLOCMEM
ALLOCMEM( int size )
#else
ALLOCMEM( char *fname, int lineno, int size )
#endif
{
void *p;
p = malloc( size );
if ( p == 0 )
{
#ifdef DEBUG_ALLOCMEM
fprintf( stdout,
"Fatal Error: Out of memory in \nfile: %s\nline: %d\n",
fname, lineno);
#else
fprintf( stdout, "Fatal Error: Out of memory!\n" );
#endif
exit( 1 );
}
memset( p, 0, size );
#ifdef DEBUG_ALLOCMEM
LogAllocMem( fname, lineno, p, size );
#endif
return p;
}
/****************************************************************************
* REALLOCMEM
*
* a function to reallocate a chunk of memory that was previously alloc'd.
****************************************************************************/
void *
#ifndef DEBUG_ALLOCMEM
REALLOCMEM( void *pOld, int size )
#else
REALLOCMEM( char *fname, int lineno, void *pOld, int size )
#endif
{
void *p;
if ( pOld == 0 ) /* act like ALLOCMEM if no prev memory */
{
#ifdef DEBUG_ALLOCMEM
return ALLOCMEM( fname, lineno, size );
#else
return ALLOCMEM( size );
#endif
}
p = realloc( pOld, size );
if ( p == 0 )
{
#ifdef DEBUG_ALLOCMEM
fprintf( stdout,
"Fatal Error: Out of memory in \nfile: %s\nline: %d\n",
fname, lineno);
#else
fprintf( stdout, "Fatal Error: Out of memory!\n" );
#endif
exit( 1 );
}
#ifdef DEBUG_ALLOCMEM
LogReAllocMem( fname, lineno, pOld, p, size );
#endif
return p;
}
/****************************************************************************
* FREEMEM
*
* a function to free some allocated memory
****************************************************************************/
void
#ifndef DEBUG_ALLOCMEM
FREEMEM( void *p )
#else
FREEMEM( char *fname, int lineno, void *p )
#endif
{
if ( p == 0 ) /* don't free NULL pointers */
{
#ifdef DEBUG_ALLOCMEM
fprintf( stdout,
"debugmem: ALERT! Attempting to free a NULL pointer.\n"
"File:%s\nLine: %d\n", fname, lineno );
#endif
return;
}
free( p );
#ifdef DEBUG_ALLOCMEM
LogFreeMem( fname, lineno, p );
#endif
}
#ifdef DEBUG_ALLOCMEM
/****************************************************************************
* LogReAllocMem
*
* the routine that handles the details of logging a call to ReAllocMem
****************************************************************************/
static void
LogReAllocMem( char *fname, int line, void *pold, void *pnew, int newsize )
{
int idx;
for( idx = AllocChain; idx != -1; idx = AllocArray[ idx ].NextAlloc )
{
if ( pold == AllocArray[ idx ].p &&
AllocArray[ idx ].InUse != 0 )
{
break;
}
}
if ( idx < 0 || idx >= MAXALLOCS )
{
fprintf( stdout, "debugmem: attempt to realloc an invalid "
"pointer from\nfile: %s\nline:%d\n", fname, line );
return;
}
ReAllocCalls++;
AllocArray[ idx ].FnameIdx = GetFileName( fname );
AllocArray[ idx ].LineNo = line;
AllocArray[ idx ].AllocNum = ++OrdinalAllocationNumber;
AllocArray[ idx ].p = pnew;
AllocArray[ idx ].Size = newsize;
AllocArray[ idx ].ReallocCount++;
}
/****************************************************************************
* LogAllocMem
*
* the routine that handles the details of logging a call to AllocMem
****************************************************************************/
static void
LogAllocMem( char *fname, int line, void *p, int size )
{
int idx;
if ( RegisteredExit == 0 )
{
atexit( DoAllocSummary );
RegisteredExit++;
}
if ( AllocChain == -1 )
{
idx = 0;
}
else
{
for( idx = 0;
AllocArray[ idx ].InUse != 0 && idx < MAXALLOCS;
idx++ )
{
/* do nothing... just setting idx */ ;
}
if ( idx >= MAXALLOCS )
{
fprintf( stdout, "debugmem: too many alloc calls; "
"increase size of \nMAXALLOCS to something "
"larger than %d\n", MAXALLOCS );
exit( 1 );
}
}
AllocCalls++;
AllocArray[ idx ].InUse = 1;
AllocArray[ idx ].FnameIdx = GetFileName( fname );
AllocArray[ idx ].LineNo = line;
AllocArray[ idx ].AllocNum = ++OrdinalAllocationNumber;
AllocArray[ idx ].p = p;
AllocArray[ idx ].Size = size;
AllocArray[ idx ].ReallocCount = 0;
AllocArray[ idx ].PrevAlloc = -1;
AllocArray[ idx ].NextAlloc = AllocChain;
if ( AllocChain != -1 )
{
AllocArray[ AllocChain ].PrevAlloc = idx;
}
AllocChain = idx;
ChainLen++;
if ( ChainLen > MaxChainLen )
{
MaxChainLen = ChainLen;
}
}
/****************************************************************************
* LogFreeMem
*
* the routine that handles the details of logging a call to FreeMem
****************************************************************************/
static void
LogFreeMem( char *fname, int line, void *pold )
{
int idx;
for( idx = AllocChain; idx != -1; idx = AllocArray[ idx ].NextAlloc )
{
if ( pold == AllocArray[ idx ].p &&
AllocArray[ idx ].InUse != 0 )
{
break;
}
}
if ( idx < 0 || idx >= MAXALLOCS )
{
fprintf( stdout, "debugmem: attempt to free an invalid "
"pointer from\nfile: %s\nline: %d\n", fname, line );
return;
}
FreeCalls++;
if ( AllocArray[ idx ].PrevAlloc == -1 )
{
if ( AllocArray[ idx ].NextAlloc == -1 )
{
/*
* prev == -1 && next == -1
* this was the only entry on the chain
*/
AllocChain = -1;
}
else
{
/*
* prev == -1 && next != -1
* this was the first entry on the chain
*/
AllocChain = AllocArray[ idx ].NextAlloc;
AllocArray[ AllocChain ].PrevAlloc = -1;
}
}
else
{
if ( AllocArray[ idx ].NextAlloc == -1 )
{
/*
* prev != -1 && next == -1
* this was the last entry on the chain
*/
AllocArray[ AllocArray[idx].PrevAlloc ].NextAlloc = -1;
}
else
{
/*
* prev != -1 && next != -1
* this was a middle entry on the chain
*/
AllocArray[ AllocArray[idx].PrevAlloc ].NextAlloc =
AllocArray[idx].NextAlloc;
AllocArray[ AllocArray[idx].NextAlloc ].PrevAlloc =
AllocArray[idx].PrevAlloc;
}
}
memset( &AllocArray[ idx ], 0, sizeof( allocinfo ));
ChainLen--;
}
/****************************************************************************
* GetFileName
*
* return the index of the filename in the table -- or insert the name into
* the table and return the index of the new entry
****************************************************************************/
static int
GetFileName( char *fname )
{
int i;
int NextEmpty = -1;
for( i = 0; i < MAXFNAMES; i++ )
{
if (( NextEmpty == -1 ) && ( FileNames[ i ][ 0 ] == '\0' ))
{
/* we've found the next empty slot */
NextEmpty = i;
break;
}
if ( strcmp( &(FileNames[ i ][0]), fname ) == 0 )
{
/* the name already exists! */
return i;
}
}
if (( NextEmpty == -1 ) ||
( i < 0 ) ||
( i >= MAXFNAMES ))
{
fprintf( stdout, "debugmem: too many files referenced;\n"
"if %d >= %d then increase size of MAXFNAMES to \n"
"something larger than %d\n", i, MAXFNAMES, MAXFNAMES );
exit( 1 );
}
strcpy( &(FileNames[ i ][0]), fname );
return i;
}
/****************************************************************************
* DoAllocSummary
*
* print the summary of info about memory allocation -- runs automatically
* at program termination via the call to atexit in LogAllocMem
****************************************************************************/
static void
DoAllocSummary( void )
{
int idx;
fprintf( stdout, "\n\nSummary of Memory Allocation\n\n" );
fprintf( stdout, "%5d calls to AllocMem\n", AllocCalls );
fprintf( stdout, "%5d calls to ReAllocMem\n", ReAllocCalls );
fprintf( stdout, "%5d calls to FreeMem\n", FreeCalls );
fprintf( stdout, "\nNote that calling ReAllocMem() with a NULL ptr\n"
"is considered a call to AllocMem()\n\n" );
fprintf( stdout, "%5d entries on allocation chain now\n", ChainLen );
fprintf( stdout, "%5d max entries on allocation chain\n", MaxChainLen );
#ifdef DEBUG_ALLOCMEM_DUMP
if ( AllocChain == -1 )
{
fprintf( stdout, "\nNo memory left allocated.\n" );
}
else
{
fprintf( stdout, "\nMemory left allocated...\n\n" );
for( idx = AllocChain;
idx != -1;
idx = AllocArray[ idx ].NextAlloc )
{
fprintf( stdout,
"Alloc #: %-5d Sz: %-4d ReAllocCount: %-3d"
" Addr: %08X\nLine: %-5d File: %s\n",
AllocArray[ idx ].AllocNum,
AllocArray[ idx ].Size,
AllocArray[ idx ].ReallocCount,
AllocArray[ idx ].p,
AllocArray[ idx ].LineNo,
&(FileNames[ AllocArray[ idx ].FnameIdx ][ 0 ])
);
DumpBytes( (unsigned char *) AllocArray[ idx ].p,
AllocArray[ idx ].Size );
fprintf( stdout, "\n" );
}
}
#endif
fprintf( stdout, "\nSummary of Memory Allocation Complete\n\n" );
}
/****************************************************************************
* DumpBytes
*
* do a hex dump of memory provided for a provided size.
****************************************************************************/
static void
DumpBytes( unsigned char *p, int sz )
{
int i;
char buff[20];
int CountPrinted = 0;
int LastLineCharsPrinted;
static char *AddrFmt = "%8X: ";
fprintf( stdout, "%9s %35s %19s\n", "-address-",
"------------- hex data ------------",
"---- ascii data ---" );
/* print whole lines of data */
while ( sz - CountPrinted >= 16 )
{
fprintf( stdout, AddrFmt, p );
fprintf( stdout, "%02X%02X%02X%02X %02X%02X%02X%02X ",
*p, *(p+1), *(p+2), *(p+3),
*(p+4), *(p+5), *(p+6), *(p+7) );
fprintf( stdout, "%02X%02X%02X%02X %02X%02X%02X%02X ",
*(p+8), *(p+9), *(p+10), *(p+11),
*(p+12), *(p+13), *(p+14), *(p+15) );
for( i = 0 ; i <= 15; i++ )
{
if ( p[i] >= ' ' && p[i] < 127 )
{
fputc( p[i], stdout );
}
else
{
fputc( '.', stdout );
}
/* print spaces between memory words */
switch( i )
{
case 3:
case 7:
case 11:
case 15:
fputc( ' ', stdout );
break;
}
}
fputc( '\n', stdout );
CountPrinted += 16;
p += 16;
}
/* print last line of data */
i = 1;
buff[0] = '\0';
LastLineCharsPrinted = 0;
if ( CountPrinted < sz )
{
fprintf( stdout, AddrFmt, p );
LastLineCharsPrinted += 14;
}
while ( CountPrinted < sz )
{
fprintf( stdout, "%02X", *p );
LastLineCharsPrinted += 2;
if ( *p >= ' ' && *p < 127 )
{
static char foo[2] = "X";
foo[0] = *p;
strcat( buff, foo );
}
else
{
strcat( buff, "." );
}
switch( i )
{
case 4:
case 8:
case 12:
fputc( ' ', stdout );
LastLineCharsPrinted++;
strcat( buff, " " );
break;
case 16:
fprintf( stdout, " " );
LastLineCharsPrinted += 4;
break;
}
CountPrinted++;
i++;
p++;
}
while ( LastLineCharsPrinted < 54 )
{
fputc( ' ', stdout );
LastLineCharsPrinted++;
}
fprintf( stdout, "%s\n", buff );
}
#endif