diff --git a/centrallix-lib/include/newmalloc.h b/centrallix-lib/include/newmalloc.h index 6022e72e..7fa0fa78 100644 --- a/centrallix-lib/include/newmalloc.h +++ b/centrallix-lib/include/newmalloc.h @@ -32,7 +32,7 @@ typedef struct _ov int Magic; struct _ov *Next; } - Overlay,*pOverlay; + Overlay, *pOverlay; #ifdef NMMALLOC_DEBUG #define BLK_LEAK_CHECK 1 @@ -44,11 +44,10 @@ typedef struct _ov #define SIZED_BLK_COUNTING 1 #endif -/** nmMalloc block caching causes Valgrind to lose track of what call - ** stack actually allocated the block to begin with. So if we're using - ** valgrind, turn off block caching altogether, and make the nmSysXyz() calls - ** just pass-throughs. - **/ +/*** nmMalloc block caching causes Valgrind to lose track of the call stack + *** where the developer allocated the block. If we are using Valgrind, this + *** caching is disabled. ALso, the nmSysXyz() call are simply pass-throughs. + ***/ #ifdef USING_VALGRIND #define NO_BLK_CACHE 1 #undef NM_USE_SYSMALLOC @@ -70,16 +69,19 @@ void nmSetErrFunction(int (*error_fn)()); void nmClear(); void nmCheckAll(); // checks for buffer overflows void* nmMalloc(int size); -void nmFree(void* ptr,int size); +void nmFree(void* ptr, int size); void nmStats(); -void nmRegister(int size,char* name); +void nmRegister(int size, char* name); +void nmPrintNames(int size); void nmDebug(); void nmDeltas(); void* nmSysMalloc(int size); void nmSysFree(void* ptr); -void* nmSysRealloc(void* ptr, int newsize); -char* nmSysStrdup(const char* ptr); +void* nmSysRealloc(void* ptr, int new_size); +char* nmSysStrdup(const char* str); +int nmSysGetSize(void* ptr); +/** Tagging system (not implemented). **/ void nmEnableTagging(); void nmRegisterTagID(int tag_id, char* name); void nmSetTag(void* ptr, int tag_id, void* tag); diff --git a/centrallix-lib/src/newmalloc.c b/centrallix-lib/src/newmalloc.c index 32c02b85..fc877c03 100644 --- a/centrallix-lib/src/newmalloc.c +++ b/centrallix-lib/src/newmalloc.c @@ -1,13 +1,3 @@ -#ifdef HAVE_CONFIG_H -#include "cxlibconfig-internal.h" -#endif -#include -#include -#include -#include -#include "magic.h" -#include "newmalloc.h" - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Base Library */ @@ -18,37 +8,51 @@ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: NewMalloc memory manager (newmalloc.c, .h) */ -/* Author: Greg Beeley (GRB) */ +/* Module: NewMalloc memory manager (newmalloc.c, .h) */ +/* Author: Greg Beeley (GRB) */ /* */ /* Description: This module provides block-caching memory allocation */ -/* and debugging services, to help find memory leaks as */ -/* well. It also interacts with the magic number module */ -/* to ensure memory allocation consistency. Slower in */ -/* debugging mode but can improve app speed dramatically */ -/* in implementation mode. While blocks returned from the */ -/* nm* routines can be mixed-and-matched with the normall */ -/* libc functions, this defeats the purpose of the system */ -/* and leads to inaccuracies in the debugging information. */ +/* and debugging services to help find memory leaks and */ +/* other memory errors. It also uses magic numbers to */ +/* ensure memory allocation consistency. This module is */ +/* slower than standard libc allocation when in debug */ +/* mode, but it is drastically faster in production mode. */ +/* */ +/* Although blocks returned from the nm* routines can be */ +/* mixed-and-matched with the normal libc functions, this */ +/* leads to inaccuracies in the debugging information and */ +/* thus defeats the purpose of having a custom library. */ /* The returned blocks from the nmSys* functions CANNOT */ /* be intermixed with normal malloc/free. In short, if */ -/* you use this library, DONT use normal malloc/free at */ -/* all.... */ +/* you use this library, do NOT use the normal libc */ +/* malloc/free at all. */ /************************************************************************/ +#ifdef HAVE_CONFIG_H +#include "cxlibconfig-internal.h" +#endif + +#include +#include +#include +#include +#include + +#include "magic.h" +#include "newmalloc.h" -/** define BUFFER_OVERFLOW_CHECKING for buffer overflow checking -*** this works off of magic numbers in the 4 bytes on either end -*** of the buffer that is returned to the user, at the cost of -*** 16 bytes of memory per buffer, and a full scan of the list -*** of allocated memory twice per nmMalloc() or nmFree() call -*** -*** the check can be made at any time from normal code by calling: -*** nmCheckAll() -*** -- this functions is still defined if BUFFER_OVERFLOW_CHECKING is -*** not defined, but it becomes a NOOP -**/ +/*** BUFFER_OVERFLOW_CHECKING adds 4 bytes of magic data to either end of + *** the memory buffer returned to the user by nmMalloc(). This allows us + *** to detect clobbered memory at the cost of increasing memory overhead + *** by 16 bytes per allocated buffer, and requires a full scan of the + *** allocated memory list twice in each nmMalloc() or nmFree() call. + *** + *** This check can also be run manually by calling nmCheckAll(). + *** + *** Note: nmCheckAll() is still defined if BUFFER_OVERFLOW_CHECKING is not + *** defined (it is a NOOP). + ***/ #ifdef BUFFER_OVERFLOW_CHECKING typedef struct _mem { @@ -56,8 +60,8 @@ typedef struct _mem struct _mem *next; int magic_start; /** not 'really' here **/ - //char data[size]; - //int magic_end; + // char data[size]; + // int magic_end; } MemStruct, *pMemStruct; #define EXTRA_MEM (3*sizeof(int)+sizeof(void*)) @@ -68,7 +72,8 @@ typedef struct _mem pMemStruct startMemList; #endif -pOverlay lists[MAX_SIZE+1]; +/** List of overlay structs, used for caching allocated memory. **/ +pOverlay lists[MAX_SIZE+1]; /* TODO: Greg - Is this 65KB global variable a problem? (On the stack, it would be...) */ int listcnt[MAX_SIZE+1]; int outcnt[MAX_SIZE+1]; int outcnt_delta[MAX_SIZE+1]; @@ -84,7 +89,7 @@ void* blks[MAX_BLOCKS]; int blksiz[MAX_BLOCKS]; #endif -int isinit=0; +bool is_init = false; int (*err_fn)() = NULL; int nmsys_outcnt[MAX_SIZE+1]; @@ -101,203 +106,293 @@ typedef struct _RB pRegisteredBlockType blknames[MAX_SIZE+1]; + +/** Initialize the NewMalloc subsystem. **/ void -nmInitialize() +nmInitialize(void) { - int i; - - for(i=0;i<=MAX_SIZE;i++) lists[i]=NULL; - for(i=0;i<=MAX_SIZE;i++) listcnt[i] = 0; - for(i=0;i<=MAX_SIZE;i++) outcnt[i] = 0; - for(i=0;i<=MAX_SIZE;i++) outcnt_delta[i] = 0; - for(i=0;i<=MAX_SIZE;i++) blknames[i] = NULL; - for(i=0;i<=MAX_SIZE;i++) usagecnt[i] = 0; - for(i=0;i<=MAX_SIZE;i++) nmsys_outcnt[i] = 0; - for(i=0;i<=MAX_SIZE;i++) nmsys_outcnt_delta[i] = 0; + for (int i = 0; i <= MAX_SIZE; i++) lists[i] = NULL; + for (int i = 0; i <= MAX_SIZE; i++) listcnt[i] = 0; + for (int i = 0; i <= MAX_SIZE; i++) outcnt[i] = 0; + for (int i = 0; i <= MAX_SIZE; i++) outcnt_delta[i] = 0; + for (int i = 0; i <= MAX_SIZE; i++) blknames[i] = NULL; + for (int i = 0; i <= MAX_SIZE; i++) usagecnt[i] = 0; + for (int i = 0; i <= MAX_SIZE; i++) nmsys_outcnt[i] = 0; + for (int i = 0; i <= MAX_SIZE; i++) nmsys_outcnt_delta[i] = 0; + #ifdef BLK_LEAK_CHECK - for(i=0;imagic_start!=MGK_MEMSTART) - { - printf("bad magic_start at %p (%p) -- 0x%08x != 0x%08x\n",MEMDATA(mem),mem,mem->magic_start,MGK_MEMSTART); - ret = -1; - } - if(ENDMAGIC(mem)!=MGK_MEMEND) - { - printf("bad magic_end at %p (%p) -- 0x%08x != 0x%08x\n",MEMDATA(mem),mem,ENDMAGIC(mem),MGK_MEMEND); - ret = -1; - } + int ret = 0; + + /** Check the starting magic value. **/ + if (mem->magic_start != MGK_MEMSTART) + { + fprintf(stderr, + "Bad magic_start at %p (%p) -- 0x%08x != 0x%08x\n", + MEMDATA(mem), mem, mem->magic_start, MGK_MEMSTART + ); + ret = -1; + } + + /** Check the ending magic value. **/ + if (ENDMAGIC(mem) != MGK_MEMEND) + { + fprintf(stderr, + "Bad magic_end at %p (%p) -- 0x%08x != 0x%08x\n", + MEMDATA(mem), mem, ENDMAGIC(mem), MGK_MEMEND + ); + ret = -1; + } + return ret; } #endif + +/*** Check the before and after magic values on all MemStructs to detect memory + *** buffer overflows. Causes a seg fault if such an overflow is detected. + ***/ void -nmCheckAll() +nmCheckAll(void) { #ifdef BUFFER_OVERFLOW_CHECKING - pMemStruct mem; - int ret=0; - mem=startMemList; - while(mem) - { - if(nmCheckItem(mem)==-1) - ret=-1; - mem=mem->next; - } - if(ret==-1) - { - printf("causing segfault to halt.......\n"); - *(int*)NULL=0; - } + int ret = 0; + + /** Traverse the memory list and check each item. **/ + for (pMemStruct mem = startMemList; mem != NULL; mem = mem->next) + if (nmCheckItem(mem) == -1) ret = -1; + + /** "Handle" error. **/ + if (ret == -1) + { + fprintf(stderr, "causing segfault to halt.......\n"); + *(int*)NULL = 0; + } #endif + + return; } + #ifdef BUFFER_OVERFLOW_CHECKING +/*** Allocate new memory, using before and after magic values. + *** + *** @param size The size of the memory buffer to be allocated. + *** @returns A pointer to the start of the allocated memory buffer. + ***/ void* nmDebugMalloc(int size) { - pMemStruct tmp; - - tmp = (pMemStruct)malloc(size+EXTRA_MEM); - if(!tmp) - return NULL; - tmp->next = startMemList; - startMemList=tmp; - tmp->size=size; - tmp->magic_start=MGK_MEMSTART; - ENDMAGIC(tmp)=MGK_MEMEND; + /** Allocate space for the data. **/ + pMemStruct tmp = (pMemStruct)malloc(size + EXTRA_MEM); + if (tmp == NULL) return NULL; + + /** Initialize data in the memory struct (including magic values). **/ + tmp->size = size; + tmp->magic_start = MGK_MEMSTART; + ENDMAGIC(tmp) = MGK_MEMEND; + + /** Prepend this mem struct to the MemList linked list. **/ + tmp->next = startMemList; + startMemList = tmp; + /** Return the memory data for the user to use. **/ return (void*)MEMDATA(tmp); } + +/*** Free a memory buffer allocated using `nmDebugMalloc()`. The before and + *** after magic values are checked and a warning is displayed if an overflow + *** has occurred (although this does not halt the program or function). + *** + *** @param ptr A pointer to the memory to be freed. + ***/ void -nmDebugFree(void *ptr) +nmDebugFree(void* ptr) { - pMemStruct tmp; - pMemStruct prev; - - tmp = MEMDATATOSTRUCT(ptr); - nmCheckItem(tmp); - if(tmp==startMemList) - { - startMemList=tmp->next; - } - else - { - prev = startMemList; - while(prev->next != tmp) - prev=prev->next; - prev->next=tmp->next; - } - free(tmp); + /** Get the mem struct for the item being freed. **/ + pMemStruct tmp = MEMDATATOSTRUCT(ptr); + + /** Verify that our data hasn't been clobbered. **/ + nmCheckItem(tmp); + + /** Remove the item from the linked list. **/ + if (tmp == startMemList) + { /* Item is at the start. */ + startMemList = tmp->next; + } + else + { /* Item is not at the start. */ + /** Traverse the linked list to find the previous item. **/ + pMemStruct prev = startMemList; + while (prev->next != tmp) + prev = prev->next; + + /** Fix the gap that will be left by freeing this item. **/ + prev->next = tmp->next; + } + + /** Free the item. **/ + free(tmp); + + return; } -void* -nmDebugRealloc(void *ptr,int newsize) + +/*** Reallocates a memory block from `nmDebugMalloc()` (or `nmDebugRealloc()`) + *** to a new size, maintaining as much data as possible. Data loss only + *** occurs if the new size is smaller, in which case bits are lost starting + *** at the end of the buffer. + *** + *** @param ptr A pointer to the current buffer (deallocated by this call). + *** @param new_size The size that the buffer should be after this call. + *** @returns The new buffer, or NULL if an error occurs. + ***/ +void* +nmDebugRealloc(void* ptr, int new_size) { - void *newptr; - int oldsize; - - if(!ptr) - return nmDebugMalloc(newsize); - newptr=(void*)nmDebugMalloc(newsize); - if(!newptr) - return NULL; - oldsize=MEMDATATOSTRUCT(ptr)->size; - memmove(newptr,ptr,oldsize); - nmDebugFree(ptr); - return newptr; + /** Behaves as nmDebugMalloc() if there is no target pointer. **/ + if (ptr == NULL) return nmDebugMalloc(new_size); + + /** Allocate new data. **/ + void* new_ptr = (void*)nmDebugMalloc(new_size); + if (new_ptr == NULL) return NULL; + + /** Move the old data. **/ + int old_size = MEMDATATOSTRUCT(ptr)->size; + memmove(new_ptr, ptr, old_size); + + /** Free the old allocation. **/ + nmDebugFree(ptr); + + return new_ptr; } #else -#define nmDebugMalloc(size) malloc(size) -#define nmDebugFree(ptr) free(ptr) -#define nmDebugRealloc(ptr,size) realloc(ptr,size) + #define nmDebugMalloc(size) malloc(size) + #define nmDebugFree(ptr) free(ptr) + #define nmDebugRealloc(ptr,size) realloc(ptr,size) #endif + void nmSetErrFunction(int (*error_fn)()) { - err_fn = error_fn; + err_fn = error_fn; + return; } + +/*** Clear the allocated memory block cache. This deallocates all memory + *** blocks that were marked as unused by a call to `nmFree()`, but were + *** moved to the cache instead of being freed to the OS memory pool. + ***/ void -nmClear() +nmClear(void) { - int i; - pOverlay ov,del; - - if (!isinit) nmInitialize(); - - for(i=MIN_SIZE;i<=MAX_SIZE;i++) + if (!is_init) nmInitialize(); + + /** Iterate over each overlay list in the cache and clear it. **/ + for (size_t size = MIN_SIZE; size <= MAX_SIZE; size++) { - ov = lists[i]; - while(ov) - { - del = ov; + pOverlay ov = lists[size]; + while (ov != NULL) + { + pOverlay del = ov; ov = ov->Next; nmDebugFree(del); } - lists[i] = NULL; + lists[size] = NULL; } - + return; } + +/*** Allocate memory using block caching. The allocated block may be supplied + *** by the cache (if available), or requested from the operating system. + *** + *** @attention Should only be used for memory that will benefit from caching + *** (e.g. a struct of a constant size). Dynamically sized memory (such as + *** variable-length strings) should be allocated using nmSysMalloc() to + *** avoid overhead from unnecessary caching. + *** + *** @param size The size of the memory block to be allocated. + *** @returns A pointer to the start of the allocated memory block. + ***/ void* nmMalloc(int size) { - void* tmp; -#ifdef BLK_LEAK_CHECK - int i; -#endif - - if (!isinit) nmInitialize(); - + void* tmp = NULL; + + if (!is_init) nmInitialize(); + +/** Handle counting. **/ #ifdef GLOBAL_BLK_COUNTING nmMallocCnt++; #endif - + +/** Handle buffer overflow check. **/ #ifdef BUFFER_OVERFLOW_CHECKING nmCheckAll(); #endif - - if (size <= MAX_SIZE && size >= MIN_SIZE) + + /** Use caching if the size can be cached. **/ + if (MIN_SIZE <= size && size <= MAX_SIZE) { +/** Handle counting. **/ #ifdef SIZED_BLK_COUNTING outcnt[size]++; usagecnt[size]++; #endif + + /** Cache check. **/ if (lists[size] == NULL) - { + { /* Miss. */ tmp = (void*)nmDebugMalloc(size); } else - { + { /* Hit. */ + /** Handle counting. **/ #ifdef GLOBAL_BLK_COUNTING nmMallocHits++; #endif - tmp = lists[size]; - ASSERTMAGIC(tmp,MGK_FREEMEM); - lists[size]=lists[size]->Next; + + /** Pop allocated memory off of the start of the cache list. **/ + tmp = lists[size]; + ASSERTMAGIC(tmp, MGK_FREEMEM); + lists[size] = lists[size]->Next; + +/** Handle counting. **/ #ifdef SIZED_BLK_COUNTING listcnt[size]--; #endif @@ -305,14 +400,22 @@ nmMalloc(int size) } else { +/** Handle counting. **/ #ifdef GLOBAL_BLK_COUNTING - nmMallocTooBig++; + nmMallocTooBig++; /* TODO: Greg - Couldn't the memory also be too small to cache? */ if (size > nmMallocLargest) nmMallocLargest = size; #endif + + /** Caching isn't supported for this size: Allocate memory normally. **/ tmp = (void*)nmDebugMalloc(size); } - - if (!tmp) + + /** TODO: Greg - We might need more docs for overlays. **/ + /*** It seems like this code block is doing too many different things. It + *** uses overlays, which I don't fully understand, so I wasn't able to + *** simplify it. + ***/ + if (tmp == NULL) { if (err_fn) err_fn("Insufficient system memory for operation."); } @@ -321,13 +424,16 @@ nmMalloc(int size) if (size >= MIN_SIZE) OVERLAY(tmp)->Magic = MGK_ALLOCMEM; } - + +/** Handle overflow check. **/ #ifdef BUFFER_OVERFLOW_CHECKING nmCheckAll(); #endif - + +/** Handle memory leak checks. **/ #ifdef BLK_LEAK_CHECK - for(i=0;i= MIN_SIZE) - ASSERTNOTMAGIC(ptr,MGK_FREEMEM); - - if (!ptr) return; - - if (!isinit) nmInitialize(); - + ASSERTNOTMAGIC(ptr, MGK_FREEMEM); + + /** If the pointer is null, no work needed. **/ + if (ptr == NULL) return; + +/** Handle counting. **/ #ifdef GLOBAL_BLK_COUNTING nmFreeCnt++; #endif - + +/** Handle overflow check. **/ #ifdef BUFFER_OVERFLOW_CHECKING nmCheckAll(); #endif - + +/** Handle memory leak check. **/ #ifdef BLK_LEAK_CHECK - for(i=0;i= MIN_SIZE) + if (size <= MAX_SIZE && size >= MIN_SIZE) { -#ifdef DUP_FREE_CHECK - tmp = lists[size]; - while(tmp) - { +/** Handle duplicate free check. **/ + #ifdef DUP_FREE_CHECK + /** Search the freed memory cache to see if this memory is there. **/ + for (pOverlay tmp = lists[size]; tmp != NULL; tmp = OVERLAY(tmp)->Next) + { ASSERTMAGIC(OVERLAY(tmp),MGK_FREEMEM); if (OVERLAY(tmp) == OVERLAY(ptr)) { - printf("Duplicate nmFree()!!! Size = %d, Address = %p\n",size,ptr); + fprintf(stderr, "Duplicate nmFree()!!! Size = %d, Address = %p\n", size, ptr); if (err_fn) err_fn("Internal error - duplicate nmFree() occurred."); return; } - tmp = OVERLAY(tmp)->Next; } -#endif -#ifdef SIZED_BLK_COUNTING - outcnt[size]--; -#endif + #endif + + /** Add the freed memory to the cache. **/ + OVERLAY(ptr)->Magic = MGK_FREEMEM; OVERLAY(ptr)->Next = lists[size]; lists[size] = OVERLAY(ptr); -#ifdef SIZED_BLK_COUNTING + ptr = NULL; + +/** Handle counting. **/ + #ifdef SIZED_BLK_COUNTING + outcnt[size]--; listcnt[size]++; -#endif - OVERLAY(ptr)->Magic = MGK_FREEMEM; + #endif } - else - { #endif + + /** Free the block if it was not consumed by the cache. **/ + if (ptr != NULL) + { nmDebugFree(ptr); -#ifndef NO_BLK_CACHE + ptr = NULL; } -#endif - + +/** Handle overflow check. **/ #ifdef BUFFER_OVERFLOW_CHECKING nmCheckAll(); #endif - + return; } +/** Print stats about the NewMalloc subsystem, for debugging. **/ void -nmStats() +nmStats(void) { - - if (!isinit) nmInitialize(); - - printf("NewMalloc subsystem statistics:\n"); - printf(" nmMalloc: %d calls, %d hits (%3.3f%%)\n", - nmMallocCnt, - nmMallocHits, - (float)nmMallocHits/(float)nmMallocCnt*100.0); - printf(" nmFree: %d calls\n", nmFreeCnt); - printf(" bigblks: %d too big, %d largest size\n\n", - nmMallocTooBig, - nmMallocLargest); - + if (!is_init) nmInitialize(); + + /** Warn if statistics are not being tracked. **/ + #ifndef GLOBAL_BLK_COUNTING + fprintf(stderr, "Warning: GLOBAL_BLK_COUNTING is disabled.\n"); + #endif + + /** Print subsystem stats. **/ + printf( + "NewMalloc subsystem statistics:\n" + " nmMalloc: %d calls, %d hits (%3.3f%%)\n" + " nmFree: %d calls\n" + " bigblks: %d too big, %d largest size\n\n", + nmMallocCnt, nmMallocHits, (float)nmMallocHits / (float)nmMallocCnt * 100.0, + nmFreeCnt, + nmMallocTooBig, nmMallocLargest + ); + return; } +/** Register a new memory size with a name, for debugging. **/ void -nmRegister(int size,char* name) +nmRegister(int size, char* name) { - pRegisteredBlockType blk; - - if (size > MAX_SIZE) return; - - blk = (pRegisteredBlockType)malloc(sizeof(RegisteredBlockType)); + pRegisteredBlockType blk = NULL; + + /** Ignore blocks too large to be cached. **/ + if (size > MAX_SIZE) return; + + /** Prepend a new RegisteredBlockType record to the list of records for this size. **/ + blk = (pRegisteredBlockType)malloc(sizeof(RegisteredBlockType)); + if (blk == NULL) return; blk->Next = blknames[size]; - blk->Size = size; - strcpy(blk->Name,name); blknames[size] = blk; + + /** Initialize values for this record. **/ blk->Magic = MGK_REGISBLK; + blk->Size = size; + strcpy(blk->Name, name); + + return; + } +/*** Print the registered names for a block of data of a given size to stdout. + *** + *** @param size The size of block to query. + ***/ +void +nmPrintNames(int size) + { + /*** Traverse the linked list that holds all registered names for the given + *** size and print each one. + **/ + for (pRegisteredBlockType blk = blknames[size]; blk != NULL; blk = blk->Next) + { + ASSERTMAGIC(blk, MGK_REGISBLK); + printf("%s ", blk->Name); + } + return; } +/** Print debug information about the newmalloc system. **/ void -nmDebug() +nmDebug(void) { - int i; - pRegisteredBlockType blk; - + /** Print the header for the block sizes table. **/ printf("size\tout\tcache\tusage\tnames\n"); - for(i=MIN_SIZE;iName); - blk = blk->Next; - } - printf("\n"); - } + /** Skip unused block sizes. **/ + if (usagecnt[size] == 0) continue; + + /** Print stats about this block size. **/ + printf("%ld\t%d\t%d\t%d\t", size, outcnt[size], listcnt[size], usagecnt[size]); + + /** Print each name for this block size. **/ + nmPrintNames(size); + + printf("\n"); } + + /** Print the header for the nmSysXYZ() info table. **/ printf("\n-----\n"); printf("size\toutcnt\n-------\t-------\n"); - for(i=MIN_SIZE;i<=MAX_SIZE;i++) + + /** Iterate through each possible block size. **/ + for (size_t size = MIN_SIZE; size <= MAX_SIZE; size++) { - if (nmsys_outcnt[i]) printf("%d\t%d\n",i,nmsys_outcnt[i]); + /** Skip unused block sizes. **/ + if (nmsys_outcnt[size] == 0) continue; + + /** Print the nmSysXYZ() block information. **/ + printf("%ld\t%d\n", size, nmsys_outcnt[size]); } printf("\n"); - + return; } +/** Print debug information about the newmalloc system. **/ void -nmDeltas() +nmDeltas(void) { - int i, total; - pRegisteredBlockType blk; - - total = 0; + /** Print the header for the block size deltas table. **/ printf("size\tdelta\tnames\n-------\t-------\t-------\n"); - for(i=MIN_SIZE;i<=MAX_SIZE;i++) + + /** Iterate through each possible block size. **/ + int total_delta = 0; + for (size_t size = MIN_SIZE; size <= MAX_SIZE; size++) { - if (outcnt[i] != outcnt_delta[i]) - { - printf("%d\t%d\t",i,outcnt[i] - outcnt_delta[i]); - total += (i * (outcnt[i] - outcnt_delta[i])); - blk = blknames[i]; - while(blk) - { - ASSERTMAGIC(blk,MGK_REGISBLK); - printf("%s ", blk->Name); - blk = blk->Next; - } - printf("\n"); - outcnt_delta[i] = outcnt[i]; - } + /** Skip entries where there is no change. **/ + if (outcnt[size] == outcnt_delta[size]) continue; + + /** Print the change and add it to the total_delta. **/ + printf("%ld\t%d\t", size, outcnt[size] - outcnt_delta[size]); + total_delta += (size * (outcnt[size] - outcnt_delta[size])); + + /** Print each name for this block size from the linked list. **/ + nmPrintNames(size); + + /** End of line. **/ + printf("\n"); + + /** Reset the delta. **/ + outcnt_delta[size] = outcnt[size]; } + + /** Print the header for the nmSysXYZ() info table. **/ printf("\nsize\tdelta\n-------\t-------\n"); - for(i=MIN_SIZE;i<=MAX_SIZE;i++) + for (size_t size = MIN_SIZE; size <= MAX_SIZE; size++) { - if (nmsys_outcnt[i] != nmsys_outcnt_delta[i]) - { - printf("%d\t%d\n",i,nmsys_outcnt[i] - nmsys_outcnt_delta[i]); - total += (i * (nmsys_outcnt[i] - nmsys_outcnt_delta[i])); - nmsys_outcnt_delta[i] = nmsys_outcnt[i]; - } + /** Skip sizes where no change in memory occurred. **/ + if (nmsys_outcnt[size] == nmsys_outcnt_delta[size]) continue; + + /** Print the results. **/ + printf("%ld\t%d\n", size, nmsys_outcnt[size] - nmsys_outcnt_delta[size]); + total_delta += (size * (nmsys_outcnt[size] - nmsys_outcnt_delta[size])); + nmsys_outcnt_delta[size] = nmsys_outcnt[size]; } printf("\n"); - printf("delta %d total bytes\n", total); - + + /** Print the total delta. **/ + /** TODO: Israel - Change this to use snprint_bytes() once that function is available. **/ + printf("delta %d total bytes\n", total_delta); + return; } +/*** Allocate memory without using block caching. The size of the allocated + *** memory is stored at the start of the memory block for debugging. + *** + *** @attention Should be used for memory that will NOT benefit from caching + *** (e.g. a variable length string). Consistently sized memory (such as + *** a struct of a constant size) should be allocated using nmMalloc() to + *** improve performance. + *** + *** @param size The size of the memory block to be allocated. + *** @returns A pointer to the start of the allocated memory block. + ***/ void* nmSysMalloc(int size) { -#ifdef NM_USE_SYSMALLOC - char* ptr; - ptr = (char*)nmDebugMalloc(size+sizeof(int)); - if (!ptr) return NULL; - *(int*)ptr = size; -#ifdef SIZED_BLK_COUNTING - if (size > 0 && size <= MAX_SIZE) nmsys_outcnt[size]++; -#endif - return (void*)(ptr+sizeof(int)); +/** Fallback if sysMalloc() is disabled. **/ +#ifndef NM_USE_SYSMALLOC + return (void*)nmDebugMalloc(size); #else - return (void*)nmDebugMalloc(size); + + /** Allocate the requested space, plus the initial size int. **/ + char* ptr = (char*)nmDebugMalloc(sizeof(int) + size); + if (ptr == NULL) return NULL; + + /** Set the size int. **/ + *(int*)ptr = size; + + /** Update sized block counting, if necessary. **/ + #ifdef SIZED_BLK_COUNTING + if (size > 0 && size <= MAX_SIZE) nmsys_outcnt[size]++; + #endif + + /** Return the allocated memory (starting after the size int). **/ + return (void*)(sizeof(int) + ptr); #endif + + return NULL; /** Unreachable. **/ } +/*** TODO: Greg - I believe that the above code is the best way to satisfy my interpretation of + *** requirement 2 of the coding style: "Every function must have a return + *** line at the end, even if the function never reaches that return." + *** Without the goofy unreachable return, the last line will be an #endif. + *** Am I following the style correctly? + ***/ + + +/*** Free a memory buffer allocated using `nmSysMalloc()` (or similar). + *** + *** @param ptr A pointer to the memory to be freed. + ***/ void nmSysFree(void* ptr) { -#ifdef NM_USE_SYSMALLOC -#ifdef SIZED_BLK_COUNTING - int size; - size = *(int*)(((char*)ptr)-sizeof(int)); - if (size > 0 && size <= MAX_SIZE) nmsys_outcnt[size]--; -#endif - nmDebugFree(((char*)ptr)-sizeof(int)); +/** Fallback if sysMalloc() is disabled. **/ +#ifndef NM_USE_SYSMALLOC + nmDebugFree(ptr); #else - nmDebugFree(ptr); + + /** Count sized blocks, if enabled. **/ + #ifdef SIZED_BLK_COUNTING + int size; + size = *(int*)(((char*)ptr)-sizeof(int)); + if (size > 0 && size <= MAX_SIZE) nmsys_outcnt[size]--; + #endif + + /** Free the initial size int, as well as the rest of the allocated memory. **/ + nmDebugFree(((char*)ptr) - sizeof(int)); #endif + return; } + +/*** Reallocates a memory block from `nmSysMalloc()` (or similar) to a new + *** size, maintaining as much data as possible. Data loss only occurs if the + *** new size is smaller, in which case bits are lost starting at the end of + *** the buffer. + *** + *** @param ptr A pointer to the current buffer (deallocated by this call). + *** @param new_size The size that the buffer should be after this call. + *** @returns The new buffer, or NULL if an error occurs. + ***/ void* -nmSysRealloc(void* ptr, int newsize) +nmSysRealloc(void* ptr, int new_size) { -#ifdef NM_USE_SYSMALLOC -#ifdef SIZED_BLK_COUNTING - int size; -#endif - char* newptr; - if (!ptr) return nmSysMalloc(newsize); -#ifdef SIZED_BLK_COUNTING - size = *(int*)(((char*)ptr)-sizeof(int)); -#endif - newptr = (char*)nmDebugRealloc((((char*)ptr)-sizeof(int)), newsize+sizeof(int)); - if (!newptr) return NULL; -#ifdef SIZED_BLK_COUNTING - if (size > 0 && size <= MAX_SIZE) nmsys_outcnt[size]--; -#endif - *(int*)newptr = newsize; -#ifdef SIZED_BLK_COUNTING - if (newsize > 0 && newsize <= MAX_SIZE) nmsys_outcnt[newsize]++; -#endif - return (void*)(newptr+sizeof(int)); +/** Fallback if sysMalloc() is disabled. **/ +#ifndef NM_USE_SYSMALLOC + return (void*)nmDebugRealloc(ptr, new_size); #else - return (void*)nmDebugRealloc(ptr,newsize); + + /** Behaves as nmSysMalloc() if there is no target pointer. **/ + if (ptr == NULL) return nmSysMalloc(new_size); + + /** If no work needs to be done, do nothing. **/ + void* buffer_ptr = ((char*)ptr) - sizeof(int); + const int size = *(int*)buffer_ptr; + if (size == new_size) return ptr; + + /** Realloc the given memory with space for the initial size int. **/ + char* new_ptr = (char*)nmDebugRealloc(buffer_ptr, sizeof(int) + new_size); + if (new_ptr == NULL) return NULL; + + /** Update the initial size int. **/ + *(int*)new_ptr = new_size; + + /** Handle counting. **/ + #ifdef SIZED_BLK_COUNTING + if (0 < size && size <= MAX_SIZE) nmsys_outcnt[size]--; + if (0 < new_size && new_size <= MAX_SIZE) nmsys_outcnt[new_size]++; + #endif + + /** Return the pointer to the new memory. **/ + return (void*)(sizeof(int) + new_ptr); #endif + + return NULL; /** Unreachable. **/ } + +/*** Duplicate a string into a new memory buffer, which is allocated by using + *** `nmSysMalloc()` (and thus, it should be freed with `nmSysFree()` to avoid + *** causing a memory leak). + *** + *** Note: The string is not deallocated by this function call. Thus, it can + *** be stack allocated, heap allocated, or even a string literal without + *** causing an error. + *** + *** @attention The str pointer is _assumed_ to point to a null-terminated + *** string. If this is not the case, the behavior is undefined, as with + *** the C standard `strdup()` function. + *** + *** @param str The string to be duplicated. + *** @returns A pointer to the new string buffer containing the string, or + *** NULL if an error occurs. + ***/ char* -nmSysStrdup(const char* ptr) +nmSysStrdup(const char* str) { -#ifdef NM_USE_SYSMALLOC - char* newptr; - int n = strlen(ptr); - newptr = (char*)nmSysMalloc(n+1); - if (!newptr) return NULL; - memcpy(newptr,ptr,n+1); - return newptr; +/** Fallback if sysMalloc() is disabled. **/ +#ifndef NM_USE_SYSMALLOC + return strdup(str); #else - return strdup(ptr); + + /** Allocate space for the string. **/ + size_t n = strlen(str) + 1u; /* Length, including the null terminator. */ + char* new_str = (char*)nmSysMalloc(n); + if (new_str == NULL) return NULL; + + /** Copy the string into the new memory. **/ + memcpy(new_str, str, n); + + return new_str; #endif + + return NULL; /** Unreachable. **/ + } + + +/*** Gets the size of memory allocated in the buffer using an `nmSysXYZ()` + *** function, if possible (depending on compiler #define flags, this size + *** might not be stored). + *** + *** @param ptr A pointer to the memory buffer to be checked. The function's + *** behavior is undefined if this pointer is a nonNULL value which does + *** NOT point to the start of a valid memory block from an `nmSysXYZ()`. + *** @returns The size of the memory buffer, if it can be found, or + *** -1 if the size of the buffere was not stored, or + *** -1 if `ptr` is NULL. + ***/ +int +nmSysGetSize(void* ptr) + { +#ifndef NM_USE_SYSMALLOC + return -1; /* Value not stored. */ +#else + if (ptr == NULL) return -1; + return *(ptr - sizeof(int)); +#endif }