Gallery Home Blog Contact

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