bjorn@dataioDataio.UUCP (Bjorn Benson) (04/29/86)
[This posting refers to an article entitled "oops, corrupted memory again!" in net.lang.c. I am posting it here because it is source.] My tool for approaching this problem is to build another level of data abstraction on top of malloc() and free() that implements some checking. This does a number of things for you: - Checks for overruns and underruns on allocated data - Keeps track of where in the program the memory was malloc'ed - Reports on pieces of memory that were not free'ed - Records some statistics such as maximum memory used - Marks newly malloc'ed and newly free'ed memory with special values You can use this scheme to: - Find bugs such as overrun, underrun, etc because you know where a piece of data was malloc'ed and where it was free'ed - Find bugs where memory was not free'ed - Find bugs where newly malloc'ed memory is used without initializing - Find bugs where newly free'ed memory is still used - Determine how much memory your program really uses - and other things To implement my scheme you must have a C compiler that has __LINE__ and __FILE__ macros. If your compiler doesn't have these then (a) buy another: compilers that do are available on UNIX 4.2bsd based systems and the PC, and probably on other machines; or (b) change my scheme somehow. I have recomendations on both these points if you would like them (e-mail please). There are 3 functions in my package: char *NEW( uSize ) Allocate memory of uSize bytes (equivalent to malloc()) FREE( pPtr ) Free memory allocated by NEW (equivalent to free()) TERMINATE() End system, report errors and stats I personally use two more functions, but have not included them here: char *STRSAVE( sPtr ) Save a copy of the string in dynamic memory char *RENEW( pPtr, uSize ) (equivalent to realloc()) --------------------------HEADER-FILE--------------------------- /* * Memory sub-system, written by Bjorn Benson */ extern char *_new(); #define NEW(SZ) _new( SZ, __FILE__, __LINE__ ) #define FREE(PTR) _free( PTR, __FILE__, __LINE__ ) -------------------------C-SOURCE-FILE------------------------- /* * Memory sub-system, written by Bjorn Benson */ #include <stdio.h> typedef unsigned uns; /* Shorthand */ struct REMEMBER_INTERNAL { struct REMEMBER *_pNext; /* Linked list of structures */ struct REMEMBER *_pPrev; /* Other link */ char *_sFileName; /* File in which memory was new'ed */ uns _uLineNum; /* Line number is above file */ uns _uDataSize; /* Size requested */ uns _lSpecialValue; /* Underrun marker value */ }; struct REMEMBER { struct REMEMBER_INTERNAL tInt; char aData[1]; }; #define pNext tInt._pNext #define pPrev tInt._pPrev #define sFileName tInt._sFileName #define uLineNum tInt._uLineNum #define uDataSize tInt._uDataSize #define lSpecialValue tInt._lSpecialValue static long lCurMemory = 0; /* Current memory usage */ static long lMaxMemory = 0; /* Maximum memory usage */ /* Note: both these refer to the NEW'ed */ /* data only. They do not include*/ /* malloc() roundoff or the extra */ /* space required by the REMEMBER */ /* structures. */ static uns cNewCount = 0; /* Number of times NEW() was called */ static struct REMEMBER *pRememberRoot = NULL; /* Root of the linked list of REMEMBERs */ #define ALLOC_VAL 0xA5 /* NEW'ed memory is filled with this */ /* value so that references to it will */ /* end up being very strange. */ #define FREE_VAL 0x8F /* FREE'ed memory is filled with this */ /* value so that references to it will */ /* also end up being strange. */ #define MAGICKEY 0x14235296 /* A magic value for underrun key */ #define MAGICEND0 0x68 /* Magic values for overrun keys */ #define MAGICEND1 0x34 /* " */ #define MAGICEND2 0x7A /* " */ #define MAGICEND3 0x15 /* " */ /* Warning: do not change the MAGICEND? values to */ /* something with the high bit set. Various C */ /* compilers (like the 4.2bsd one) do not do the */ /* sign extension right later on in this code and */ /* you will get erroneous errors. */ /* * _new( uns uSize, char *sFile, uns uLine ) : char* * Allocate some memory. */ char *_new( uSize, sFile, uLine ) uns uSize,uLine; char *sFile; { extern char *malloc(); struct REMEMBER *pTmp; char *pPtr; /* Allocate the physical memory */ pTmp = (struct REMEMBER *) malloc( sizeof(struct REMEMBER_INTERNAL) /* REMEMBER data */ + uSize /* size requested */ + 4 /* overrun mark */ ); /* Check if there isn't anymore memory avaiable */ if( pTmp == NULL ) { fprintf(stderr,"Out of memory at line %d, \"%s\"\n", uLine, sFile ); fprintf(stderr,"\t(memory in use: %ld bytes (%ldk))\n", lMaxMemory, (lMaxMemory + 1023L)/1024L ); exit(1); } /* Fill up the structure */ pTmp->lSpecialValue = MAGICKEY; pTmp->aData[uSize+0] = MAGICEND0; pTmp->aData[uSize+1] = MAGICEND1; pTmp->aData[uSize+2] = MAGICEND2; pTmp->aData[uSize+3] = MAGICEND3; pTmp->sFileName = sFile; pTmp->uLineNum = uLine; pTmp->uDataSize = uSize; pTmp->pNext = pRememberRoot; pTmp->pPrev = NULL; /* Add this REMEMBER structure to the linked list */ if( pRememberRoot ) pRememberRoot->pPrev = pTmp; pRememberRoot = pTmp; /* Keep the statistics */ lCurMemory += uSize; if( lCurMemory > lMaxMemory ) lMaxMemory = lCurMemory; cNewCount++; /* Set the memory to the aribtrary wierd value */ for( pPtr = &pTmp->aData[uSize]; pPtr > &pTmp->aData[0]; ) *(--pPtr) = ALLOC_VAL; /* Return a pointer to the real data */ return( &(pTmp->aData[0]) ); } /* * _free( char *pPtr, char *sFile, uns uLine ) * Deallocate some memory. */ _free( pPtr, sFile, uLine ) char *pPtr,*sFile; uns uLine; { struct REMEMBER *pRec; char *pTmp; uns uSize; /* Check if we have a non-null pointer */ if( pPtr == NULL ) { fprintf(stderr,"Freeing NULL pointer at line %d, \"%s\"\n", uLine, sFile ); return; } /* Calculate the address of the REMEMBER structure */ pRec = (struct REMEMBER *)( pPtr - (sizeof(struct REMEMBER_INTERNAL)) ); /* Check to make sure that we have a real REMEMBER structure */ /* Note: this test could fail for four reasons: */ /* (1) The memory was already free'ed */ /* (2) The memory was never new'ed */ /* (3) There was an underrun */ /* (4) A stray pointer hit this location */ /* */ if( pRec->lSpecialValue != MAGICKEY ) { fprintf(stderr,"Freeing unallocated data at line %d, \"%s\"\n", uLine, sFile ); return; } /* Check for an overrun */ uSize = pRec->uDataSize; if( pRec->aData[uSize+0] != MAGICEND0 || pRec->aData[uSize+1] != MAGICEND1 || pRec->aData[uSize+2] != MAGICEND2 || pRec->aData[uSize+3] != MAGICEND3 ) { fprintf(stderr,"Memory being free'ed at line %d, \"%s\" \ was overrun\n", uLine, sFile ); fprintf(stderr,"\t(allocated at line %d, \"%s\")\n", pRec->uLineNum, pRec->sFileName ); return; } /* Remove this structure from the linked list */ if( pRec->pPrev ) pRec->pPrev->pNext = pRec->pNext; else pRememberRoot = pRec->pNext; if( pRec->pNext ) pRec->pNext->pPrev = pRec->pPrev; /* Mark this data as free'ed */ for (pTmp = &pRec->aData[pRec->uDataSize]; pTmp > &pRec->aData[0] ; ) *(--pTmp) = FREE_VAL; pRec->lSpecialValue = ~MAGICKEY; /* Handle the statistics */ lCurMemory -= pRec->uDataSize; cNewCount--; /* Actually free the memory */ free( (char*)pRec ); } /* * TERMINATE() * Report on all the memory pieces that have not been * free'ed as well as the statistics. */ TERMINATE() { struct REMEMBER *pPtr; /* Report the difference between number of calls to */ /* NEW and the number of calls to FREE. >0 means more */ /* NEWs than FREEs. <0, etc. */ if( cNewCount ) fprintf(stderr,"cNewCount: %d\n", cNewCount ); /* Report on all the memory that was allocated with NEW */ /* but not free'ed with FREE. */ if( pRememberRoot != NULL ) fprintf(stderr, "Memory that was not free'ed:\n"); pPtr = pRememberRoot; while( pPtr ) { fprintf(stderr,"\t%5d bytes at 0x%06x, \ allocated at line %3d in \"%s\"\n", pPtr->uDataSize, &(pPtr->aData[0]), pPtr->uLineNum, pPtr->sFileName ); pPtr = pPtr->pNext; } /* Report the memory usage statistics */ fprintf(stderr,"Maximum memory usage: %ld bytes (%ldk)\n", lMaxMemory, (lMaxMemory + 1023L)/1024L ); }