memory provides a debugging interface for malloc and free. It is designed to provide diagnostics without changing the functional behavior of malloc and free, and it may be omitted entirely from a project, without effect.
Copyright © 2003, 2004 Dave Bayer. Subject to the terms and conditions of the MIT License.
If MEMORYDEBUG is defined, then diagnostics are run on allocated memory, in hopes of detecting memory bugs.
#define MEMORYDEBUG1 1
memoryVerify is defined as memoryVerifyDebug or memoryVerifyCheck, depending on whether or not MEMORYDEBUG is defined.
#if MEMORYDEBUG #define memoryVerify memoryVerifyDebug #else #define memoryVerify memoryVerifyCheck #endif
malloc is defined as mallocDebug or mallocCheck, depending on whether or not MEMORYDEBUG is defined.
#if MEMORYDEBUG #define malloc(X) mallocDebug( (X), __FILE__, __LINE__, 0 ) #else #define malloc mallocCheck #endif
free is defined as freeDebug or freeCheck, depending on whether or not MEMORYDEBUG is defined. In either case, a handle is passed so the freed pointer can be set to null.
#if MEMORYDEBUG #define free(X) freeDebug( (X), __FILE__, __LINE__ ) #else #define free freeCheck #endif
mallocCount reports to stdout the number of not yet freed, malloc allocations, if this count is nonzero. If MEMORYDEBUG is defined, then mallocCount also reveals an example of a not yet freed allocation.
#if MEMORYDEBUG #define mallocCount mallocCountDebug #else #define mallocCount mallocCountCheck #endif
Function prototypes:
#if MEMORYDEBUG void memoryVerifyDebug( void ); void *mallocDebug( size_t size, char *fileName, int lineNo, int ours ); void freeDebug( void *p, char *fileName, int lineNo ); void mallocCountDebug( void ); #else void memoryVerifyCheck( void ); void *mallocCheck( size_t size ); void freeCheck( void *p ); void mallocCountCheck( void ); #endif
Copyright © 2003, 2004 Dave Bayer. Subject to the terms and conditions of the MIT License.
#include <stdio.h> #include <stdlib.h> #include <ctype.h> #include "memory.h"
Undefine the malloc and free macros, so that code here can call the system versions.
#undef malloc #undef free
mallocN is used to count memory allocations, whether or not MEMORYDEBUG is defined.
static int mallocN = 0;
If MEMORYDEBUG is defined, then individually track all memory allocations.
#if MEMORYDEBUG
Check all of allocated memory for overwrites every EVERYN calls to malloc and free.
#define EVERYN 4096
Memory allocations are padded before and after with PAD bytes of memory, which should be a multiple of the byte alignment modulus. If bus errors persist with MEMORYDEBUG defined, try increasing the value for PAD.
#define PAD 32
FILL is the byte value used to clear the padding around memory allocations, and used to clear freed memory. A value other than 0 will detect a string terminator '\0' written into cleared memory.
#define FILL 1
A memRecord records information about a memory allocation.
typedef struct memRecord
{
char *fileName;
int lineNo;
size_t size;
int freed, ours;
int count;
void *p;
struct memRecord *next;
} memRecord;
#define memN 4093
static memRecord *memRecordPool, *memRecordArray[memN], spare, *memRecordSpare = &spare;
memRecordNew initializes a new memRecord, and inserts it in memRecordArray.
static void memRecordNew( void *p, char *fileName, int lineNo, size_t size, int ours, int count )
{
memRecord *r;
int i;
We need a spare memRecord to cover the call to mallocDebug, to prevent an infinite recursion.
Changing 101 to 100 will cause a bus error if PAD is 16, but will produce an overwrite that gets caught if PAD is 32.
if ( memRecordPool == 0 )
{
memRecordPool = memRecordSpare;
r = mallocDebug( 101 * sizeof( *r ), __FILE__, __LINE__, 1 );
for ( i=0; i<100; ++i )
{
r->next = memRecordPool;
memRecordPool = r++;
}
memRecordSpare = r;
r->next = 0;
}
r = memRecordPool;
memRecordPool = r->next;
i = ( (int) p ) % memN;
r->next = memRecordArray[i];
memRecordArray[i] = r;
r->fileName = fileName;
r->lineNo = lineNo;
r->size = size;
r->freed = 0;
r->ours = ours;
r->count = count;
r->p = p;
if ( count == 0 )
{
By putting a debugging break here, one can examine a memory allocation that later causes an error.
i = 0;
}
}
clearMem writes FILL to a memory range [from, to).
static void clearMem( void *p, int from, int to )
{
char *s, *t;
for ( s=t=p, s+=from, t+=to; s<t; ++s )
*s = FILL;
}
checkMem checks that a memory range [from, to) contains FILL, returning 1 on failure and 0 on success.
static int checkMem( void *p, int from, int to )
{
char *s, *t;
for ( s=t=p, s+=from, t+=to; s<t; ++s )
if ( *s != FILL ) return 1;
return 0;
}
abortMem aborts execution after displaying an error message.
static void abortMem( char *msg, int count, char *fileName, int lineNo )
{
fflush( stdout );
fprintf( stderr, "\n%s: allocation %d, file \"%s\", line %d\nAborting\n", msg, count, fileName, lineNo );
exit( 1 );
}
memoryVerifyDebug is the debugging version of memoryVerify. It checks that no memory cleared with FILL has been overwritten.
void memoryVerifyDebug( void )
{
memRecord *r, **pr;
int i;
for ( i=0, pr=memRecordArray; i<memN; ++i, ++pr )
for ( r=*pr; r!=0; r=r->next )
{
if ( r->freed && checkMem( r->p, PAD, r->size + PAD ))
abortMem( "memory written after free", r->count, r->fileName, r->lineNo );
if ( checkMem( r->p, 0, PAD ) || checkMem( r->p, r->size + PAD, r->size + 2*PAD ))
abortMem( "memory written outside bounds", r->count, r->fileName, r->lineNo );
}
}
mallocDebug is a debugging wrapper around malloc.
void *mallocDebug( size_t size, char *fileName, int lineNo, int ours )
{
void *p;
static int count = 0;
if ( ++count % EVERYN == 0 ) memoryVerifyDebug();
if ( !ours ) ++mallocN;
p = malloc( size + 2*PAD );
if ( p == 0 )
abortMem( "malloc returned null", count, fileName, lineNo );
memRecordNew( p, fileName, lineNo, size, ours, count );
clearMem( p, 0, PAD );
clearMem( p, size + PAD, size + 2*PAD );
return p + PAD;
}
freeDebug is a debugging wrapper around free. Memory is cleared but not freed.
void freeDebug( void *p, char *fileName, int lineNo )
{
memRecord *r;
static int count = 0;
int i;
if ( ++count % EVERYN == 0 ) memoryVerifyDebug();
--mallocN;
p -= PAD;
i = ( (int) p ) % memN;
for ( r=memRecordArray[i]; r!=0; r=r->next )
if ( r->p == p )
{
if ( r->freed )
abortMem( "memory freed twice", r->count, fileName, lineNo );
r->freed = 1;
if ( checkMem( p, 0, PAD ) || checkMem( p, r->size + PAD, r->size + 2*PAD ))
abortMem( "memory written outside bounds", r->count, r->fileName, r->lineNo );
clearMem( p, PAD, r->size + PAD );
return;
}
abortMem( "non-malloc memory freed", r->count, fileName, lineNo );
}
mallocCountDebug reports to stdout the number of not yet freed, malloc allocations, if this count is nonzero, and reveals an example of a not yet freed allocation.
void mallocCountDebug( void )
{
memRecord *r, **pr;
int i;
memoryVerifyDebug();
if ( mallocN != 0 )
{
printf( "malloc allocations in use = %d\n", mallocN );
for ( i=0, pr=memRecordArray; i<memN; ++i, ++pr )
for ( r=*pr; r!=0; r=r->next )
if ( !r->freed && !r->ours )
{
printf( "\nmemory not freed: allocation %d, file \"%s\", line %d\n",
r->count, r->fileName, r->lineNo );
return;
}
}
}
If MEMORYDEBUG is not defined, then only carry out checks that do not significantly degrade execution time and space.
#else
memoryVerifyCheck is the nondebugging version of memoryVerify. At present it does nothing.
void memoryVerifyCheck( void )
{
}
mallocCheck is a wrapper around malloc.
void *mallocCheck( size_t size )
{
++mallocN;
return malloc( size );
}
freeCheck is a wrapper around free.
void freeCheck( void *p )
{
--mallocN;
free( p );
}
mallocCountCheck reports to stdout the number of not yet freed, malloc allocations, if this count is nonzero.
void mallocCountCheck( void )
{
if ( mallocN != 0 )
printf( "malloc allocations in use = %d\n", mallocN );
}
#endif