/*============================================================================= MEMORY.C: Routines to manage memory =============================================================================*/ #include #include #include #include "types.h" #include "debug.h" #include "key.h" #include "task.h" #include "memory.h" #if MEM_VOLATILE_CLEARING #include #include "crc32.h" #include "randy.h" #endif /*============================================================================= Data: =============================================================================*/ //main memory pool mempool memMainPool; //growth pools mempool memGrowthPool[MGH_NumberGrowthHeaps]; sdword memNumberGrowthPools = 0; memgrowcallback memGrowthAllocate = NULL; //flag set when module started sdword memModuleInit = FALSE; //memory statistics #if MEM_STATISTICS sdword memNumberWalks; sdword memNumberAllocs; sdword memAllocPool; char memStatString[256]; bool memStatsLogging; taskhandle memStatsTaskHandle = 0xffffffff; memcookiename memStatsCookieNames[MS_NumberCookieNames]; #endif #if MEM_FILE_NV FILE *memNonVolatileFile; #endif memsmallheapinfo memSmallHeapInfo[] = { {64, 3072}, //192k {256, 768}, //192k {384, 512}, //192k {512, 128}, //64k {1024, 32}, //64k {2048, 16}, //32k {0, 0} //total: 736k }; sdword memSmallBlockHeapMaxSize = 0; #if MEM_LOG_MOSTVOLATILE volatilestat memVolatileStat[MEM_NumberVolatileStats]; #endif #if MEM_VOLATILE_CLEARING udword memClearSetting = MEM_ClearSetting; udword memFreeSetting = MEM_FreeSetting; #else #define memClearSetting MEM_ClearSetting #define memFreeSetting MEM_FreeSetting #endif /*============================================================================= Functions: =============================================================================*/ #if MEM_STATISTICS /*----------------------------------------------------------------------------- Name : memCookieNameSort Description : qsort callback for sorting cookie names by number of allocs Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ int memCookieNameSort(const void *p0, const void *p1) { return(((memcookiename *)p1)->nAllocs - ((memcookiename *)p0)->nAllocs); } /*----------------------------------------------------------------------------- Name : memStatsTaskFunction Description : Task function for creting printable strings of allocation frequency by cookie name. Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ #pragma optimize("gy", off) //turn on stack frame (we need ebp for this function) void memStatsTaskFunction(void) { static sdword index, nPrinted; taskYield(0); while (1) { taskStackSaveCond(0); if (memStatsLogging) { sprintf(memStatString, "Walks: %d, Allocs: %d, Factor: %.2f, Average pool: %.2f", memNumberWalks, memNumberAllocs, memNumberAllocs == 0 ? 0.0f : (real32)memNumberWalks / (real32)memNumberAllocs, memNumberAllocs == 0 ? 1.0f : (real32)memAllocPool / (real32)memNumberAllocs); memNumberAllocs = 0; memNumberWalks = 0; memAllocPool = 0; for (index = 0; index < MS_NumberCookieNames; index++) { if (memStatsCookieNames[index].name[0] != 0) { //if there is a name in this one nPrinted = sprintf(memStatsCookieNames[index].outputString, "%s - %d (%d)", memStatsCookieNames[index].name, memStatsCookieNames[index].nAllocs, memStatsCookieNames[index].nTotalSize / memStatsCookieNames[index].nAllocs); dbgAssert(nPrinted < MS_OutputStringLength); } else { //no name, blank the string memStatsCookieNames[index].outputString[0] = 0; } memStatsCookieNames[index].name[0] = 0; //now there's a printable string and no cookie name (ready to be re-used) } qsort(memStatsCookieNames, MS_NumberCookieNames, sizeof(memcookiename), memCookieNameSort); } taskStackRestoreCond(); taskYield(0); } } #pragma optimize("", on) /*----------------------------------------------------------------------------- Name : memCookieNameAdd Description : Increment the frequency counter of a particular cookie Inputs : name - name of cookie as passed to memAlloc length - length of allocated cookie Outputs : Return : ----------------------------------------------------------------------------*/ void memCookieNameAdd(char *name, sdword length) { sdword index; for (index = 0; index < MS_NumberCookieNames; index++) { if (memStatsCookieNames[index].name[0] == 0) { //no name defined yet memStrncpy(memStatsCookieNames[index].name, name, MS_NameStringLength); //new cookie name not yet encountered memStatsCookieNames[index].nAllocs = 1; memStatsCookieNames[index].nTotalSize = length; break; } if (!strcmp(name, memStatsCookieNames[index].name)) { //if this is the string memStatsCookieNames[index].nAllocs++; //increment the alloc counter memStatsCookieNames[index].nTotalSize += length; break; } } } #endif /*----------------------------------------------------------------------------- Name : memStartup Description : Starts the memory allocation module. Call before any other functions. Inputs : heapStart - start of heap to use. Should be aligned on at lease 4-bit boundary. heapSize - size of heap to create grow - heap growth callback Outputs : Global heap set up and (optionally) cleared. Return : OKAY if success. ----------------------------------------------------------------------------*/ sdword memStartup(void *heapStart, sdword heapSize, memgrowcallback grow) { if ((sizeof(mbhcookie) != 32) || (sizeof(memcookie) != 32)) { dbgFatalf(DBG_Loc, "Bad cookie size: %d, %d", sizeof(mbhcookie), sizeof(memcookie)); } #if MEM_ERROR_CHECKING if (memModuleInit == TRUE) dbgFatal(DBG_Loc, "Memory module started more than once."); #endif //MEM_ERROR_CHECKING dbgAssert(heapStart != NULL && heapSize > MEM_BlockSize * 20); #if MEM_VERBOSE_LEVEL >= 2 dbgMessagef("\nMemory module init. Heap = 0x%x, Length = %d", heapStart, heapSize); #endif //MEM_VERBOSE_LEVEL >= 2 memMainPool.wholePool = heapStart; memMainPool.pool = (ubyte *)(((udword)((ubyte *)heapStart + sizeof(memcookie) - 1)) & (~(sizeof(memcookie) - 1))); memMainPool.poolLength = memMainPool.heapLength = heapSize; memModuleInit = TRUE; #if MEM_FILE_NV memNonVolatileFile = fopen("mem.nv", "wt"); if (memNonVolatileFile == NULL) { dbgMessage("\nError creating log file mem.nv."); } #endif memGrowthAllocate = grow; #if MEM_VOLATILE_CLEARING { char *userName; if ((userName = getenv("USERNAME")) != NULL) { memClearSetting = (udword)crc32Compute((ubyte *)userName, strlen(userName)); } else { memClearSetting = (udword)ranRandom(RAN_ETG); } if ((userName = getenv("USERPROFILE")) != NULL) { memFreeSetting = (udword)crc32Compute((ubyte *)userName, strlen(userName)); } else { memFreeSetting = (udword)ranRandom(RAN_ETG); } } #endif return(memReset()); } /*----------------------------------------------------------------------------- Name : memPoolReset Description : Reset the specified pool. Clears everything out. Inputs : pool - pool to clear out heapStart - the address of the actual RAM this pool manages. heapSize - size of the heap, in bytes bSmallHeaps - small heaps for this pool. Does it use them? Outputs : Return : ----------------------------------------------------------------------------*/ void memPoolReset(mempool *pool, void *heapStart, sdword heapSize, bool bSmallHeaps) { ubyte *poolData; #if MEM_SMALL_BLOCK_HEAP sdword index, j; mbheap *heap; #endif pool->wholePool = heapStart; pool->pool = (ubyte *)(((udword)((ubyte *)heapStart + sizeof(memcookie) - 1)) & (~(sizeof(memcookie) - 1))); pool->poolLength = pool->heapLength = heapSize; memInitCheck(); #if MEM_CLEAR_MEM memClearDword(pool->pool, memFreeSetting, pool->poolLength / sizeof(udword)); #endif poolData = pool->pool; if (bSmallHeaps) { #if MEM_SMALL_BLOCK_HEAP dbgAssert(sizeof(memcookie) == sizeof(mbhcookie)); //set up the small block allocation pools //first pass: allocate the heap header structures for (index = 0; memSmallHeapInfo[index].blockSize != 0; index++) { //for each differing block size heap = (mbheap *)poolData; poolData += sizeof(mbheap); #if MEM_ERROR_CHECKING heap->validation = MEM_HeapValidation; #endif heap->blockSize = memSmallHeapInfo[index].blockSize; heap->nBlocks = (sdword)((real32)memSmallHeapInfo[index].nBlocks * (real32)heapSize / (real32)MEM_HeapSizeDefault); dbgAssert(heap->nBlocks != 0); listInit(&heap->allocated); listInit(&heap->free); memSmallBlockHeapMaxSize = max(memSmallBlockHeapMaxSize, heap->blockSize); #if MEM_ERROR_CHECKING if (index > 0) { dbgAssert(heap->blockSize >= memSmallHeapInfo[index - 1].blockSize); } #endif #if MEM_SMALLBLOCK_STATS heap->nAllocationAttempts = 0; heap->nOverflowedAllocations = 0; heap->usage = 0.0f; heap->firstAllocTime = taskTimeElapsed; #endif } //next pass: allocate and init the heaps and all blocks in them. for (index = 0; memSmallHeapInfo[index].blockSize != 0; index++) { //for each differing block size heap = (mbheap *)(pool->pool + index * sizeof(mbheap)); heap->heapStart = (mbhcookie *)poolData; //save start of heap for (j = 0; j < heap->nBlocks; j++) { //for all blocks we're putting in this block ((mbhcookie *)poolData)->flags = MBF_VerifyValue | MBF_SmallBlockHeap;//give it a validation key listAddNode(&heap->free, &((mbhcookie *)poolData)->link, (mbhcookie *)poolData); //add the node to the free list mbhNameSet((mbhcookie *)poolData, MEM_HeapFree); //say the cookie is free poolData += heap->blockSize + sizeof(mbhcookie); //update the poolData pointer } heap->heapEnd = (mbhcookie *)poolData; } #endif //MEM_SMALL_BLOCK_HEAP } //set pointer to first free memory cookie pool->first = pool->firstFree = (memcookie *)memRoundUp((udword)poolData); //get length of newly sized pool pool->heapLength = memRoundDown(pool->pool + pool->poolLength - (ubyte *)pool->first); dbgAssert(pool->heapLength > 1); //set pointer to end of pool pool->lastFree = pool->last = (memcookie *)((ubyte *)pool->first + pool->heapLength - sizeof(memcookie) * 2); //init first free cookie pool->firstFree->flags = MBF_VerifyValue; pool->firstFree->blocksNext = memBlocksCompute(pool->firstFree, pool->last) - 1; pool->firstFree->blocksPrevious = -1; memNameSet(pool->firstFree, MEM_NameHeap); pool->last->blocksPrevious = memBlocksCompute(pool->firstFree, pool->last) - 1; pool->last->flags = MBF_VerifyValue; //clear out all the cookie size pointers memset(pool->firstSize, 0, sizeof(pool->firstSize)); for (index = 0; index < MSB_NumberSizes + 1; index++) { //check all the min size pointers pool->lastSize[index] = pool->last; } } /*----------------------------------------------------------------------------- Name : memReset Description : Reset the memory module and optionally clear all memory. Inputs : void Outputs : Global heap set up and (optionally) cleared. Return : OKAY on success. ----------------------------------------------------------------------------*/ sdword memReset(void) { #if MEM_STATISTICS || MEM_LOG_MOSTVOLATILE sdword index; #endif memInitCheck(); memPoolReset(&memMainPool, memMainPool.wholePool, memMainPool.poolLength, TRUE); //!!! reset the growth pools - onlt needed if this function is ever called at times other than startup #if MEM_MODULE_TEST { ubyte *a, *b, *c, *d, *e, *f; a = memAlloc(10, "TEST_A", 0); b = memAlloc(100, "TEST_B", 0); c = memAlloc(1, "TEST_C", 0); d = memAlloc(64, "TEST_D", 0); e = memAlloc(10000, "TEST_E", 0); f = memAlloc(10, "TEST_F", 0); memFree(b); memFree(c); memFree(d); memFree(f); memDefragment(); } #endif // MEM_MODULE_TEST #if MEM_STATISTICS memNumberWalks = 0; memNumberAllocs = 0; memAllocPool = 0; memStatsLogging = FALSE; memStatString[0] = 0; for (index = 0; index < MS_NumberCookieNames; index++) { memStatsCookieNames[index].name[0] = 0; memStatsCookieNames[index].nAllocs = 0; memStatsCookieNames[index].outputString[0] = 0; } #endif #if MEM_LOG_MOSTVOLATILE //clear out all the volatile stat entries for (index = 0; index < MEM_NumberVolatileStats; index++) { memVolatileStat[index].name[0] = 0; memVolatileStat[index].nLogged = 0.0f; memVolatileStat[index].totalTime = 0.0f; } #endif return(OKAY); } /*----------------------------------------------------------------------------- Name : memClose Description : Closes the memory allocation module, preventing further allocations from having any effect. Inputs : freeMem - function to call for freeing growth heaps Outputs : clears the module init flag Return : OKAY ----------------------------------------------------------------------------*/ sdword memClose(memgrowthfreecallback freeMem) { sdword index; if (!memModuleInit) { return(OKAY); } #if MEM_FILE_NV if (memNonVolatileFile) { fclose(memNonVolatileFile); } #endif for (index = 0; index < memNumberGrowthPools; index++) { freeMem(memGrowthPool[index].wholePool); } memModuleInit = FALSE; return(OKAY); } /*----------------------------------------------------------------------------- Name : memClearDword Description : Clears the specified number of dwords to the specified pattern Inputs : dest - pointer to location to recieve pattern pattern - dword to be duplicated nDwords - number of dwords to duplicate Outputs : Return : OKAY ----------------------------------------------------------------------------*/ sdword memClearDword(void *dest, udword pattern, sdword nDwords) { _asm { mov edi,dest mov ecx,nDwords mov eax,pattern rep stosd } return(OKAY); } /*----------------------------------------------------------------------------- Name : memNameSetFunction Description : Sets the name of specified cookie Inputs : cookie - pointer to memcookie structure name - string to be copied Outputs : cookie->name = string Return : void Note : If length of string is >= MEM_NameLength, it will be truncated at MEM_NameLength - 1 characters. It will be NULL terminated. ----------------------------------------------------------------------------*/ #if MEM_USE_NAMES sdword memNameSetFunction(memcookie *cookie, char *name) { memInitCheck(); memCookieVerify(cookie); memStrncpy(cookie->name, name, MEM_NameLength); cookie->name[MEM_NameLength - 1] = 0; #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemNameSet: Cookie at 0x%x named to '%s'", cookie, cookie->name); #endif return(OKAY); } #if MEM_SMALL_BLOCK_HEAP sdword mbhNameSetFunction(mbhcookie *cookie, char *name) { memInitCheck(); memCookieVerify(cookie); memStrncpy(cookie->name, name, MBH_NameLength); cookie->name[MBH_NameLength - 1] = 0; #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemNameSet: SBH cookie at 0x%x named to '%s'", cookie, cookie->name); #endif return(OKAY); } #endif //MEM_SMALL_BLOCK_HEAP #endif //MEM_USE_NAMES /*----------------------------------------------------------------------------- Name : memNameSetLongFunction Description : Sets the name of specified cookie Inputs : cookie - pointer to memcookie structure name - string to be copied Outputs : cookie->name = end of string Return : void Note : If length of string is >= MEM_NameLength the last MEM_NameLength characters will be used. ----------------------------------------------------------------------------*/ #if MEM_USE_NAMES sdword memNameSetLongFunction(memcookie *cookie, char *name) { memInitCheck(); memCookieVerify(cookie); if (strlen(name) >= MEM_NameLength - 1) { name = &name[strlen(name) - MEM_NameLength - 1]; } memStrncpy(cookie->name, name, MEM_NameLength); cookie->name[MEM_NameLength - 1] = 0; #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemNameSetLong: Cookie at 0x%x named to '%s'", cookie, cookie->name); #endif return(OKAY); } #endif //MEM_USE_NAMES /*----------------------------------------------------------------------------- Name : memRename Description : Renames a memory block by pointer, Inputs : pointer - memory whose cookie is to be renamed newName - string to be copied to name part of associated cookie Outputs : cookie->name = string Return : void Note : If length of string is >= MEM_NameLength, it will be truncated at MEM_NameLength - 1 characters. It will be NULL terminated. ----------------------------------------------------------------------------*/ #if MEM_USE_NAMES void memRename(void *pointer, char *newName) { memcookie *cookie; memInitCheck(); cookie = (memcookie *)pointer; cookie--; //get pointer to cookie dbgAssert(!bitTest(cookie->flags, MBF_SmallBlockHeap)); //can't rename blocks from small block heap memNameSet(cookie, newName); bitSet(cookie->flags, MBF_String); //prevent this block from going through volatility statistics } #endif //MEM_USE_NAMES #if MEM_ANAL_CHECKING /*----------------------------------------------------------------------------- Name : memPoolAnalCheck Description : Anally check the state of a memory pool, verifying nothing is amiss Inputs : pool - what pool to verify Outputs : Return : ----------------------------------------------------------------------------*/ void memPoolAnalCheck(mempool *pool) { sdword index; for (index = 0; index < MSB_NumberSizes + 1; index++) { if (pool->firstSize[index] != NULL) { memCookieVerify(pool->firstSize[index]); } memCookieVerify(pool->lastSize[index]); } } #endif /*----------------------------------------------------------------------------- Name : memSBHGrowBy Description : Grow a small block heap by a specified amount Inputs : heap - what heap to grow nNewCookies - how much to grow it by blockSize - size to grow by Outputs : Return : void ----------------------------------------------------------------------------*/ void memSBHGrowBy(mbheap *heap, sdword nNewCookies) { sdword index; ubyte *poolData; poolData = memAlloc(nNewCookies * (heap->blockSize + sizeof(mbhcookie)), "SBHGrow", NonVolatile); for (index = 0; index < nNewCookies; index++) { ((mbhcookie *)poolData)->flags = MBF_VerifyValue | MBF_SmallBlockHeap;//give it a validation key listAddNode(&heap->free, &((mbhcookie *)poolData)->link, (mbhcookie *)poolData); //add the node to the free list mbhNameSet((mbhcookie *)poolData, MEM_HeapFree); //say the cookie is free poolData += heap->blockSize + sizeof(mbhcookie); //update the poolData pointer } heap->nBlocks += nNewCookies; } /*----------------------------------------------------------------------------- Name : memAllocFunctionSBH Description : Attempts to allocate a block of RAM from a small block heap. Inputs : length - length of requested block in bytes name(optional) - name of block flags - allocation control flags Outputs : Newly allocated block may be cleared. Return : Pointer to newly allocated block, or NULL if failure. ----------------------------------------------------------------------------*/ #if MEM_SMALL_BLOCK_HEAP #if MEM_USE_NAMES void *memAllocFunctionSBH(sdword length, char *name, udword flags) #else void *memAllocFunctionSBH(sdword length, udword flags) #endif { mbheap *heap; memsmallheapinfo *info; mbhcookie *cookie; ubyte *newPointer = NULL; sdword nNewCookies; //make sure it can fit in a small block if (length > memSmallBlockHeapMaxSize) { //if block to be allocated is too big #if MEM_VERBOSE_LEVEL >= 2 dbgMessagef("\nmemAllocFunctionSBH: cannot allocate block of size %d (> %d)", length, memSmallBlockHeapMaxSize); #endif return(NULL); } for (heap = (mbheap *)memMainPool.pool, info = memSmallHeapInfo; info->blockSize != 0; heap++, info++) { //search through all small block heap structures #if MEM_ERROR_CHECKING if (heap->validation != MEM_HeapValidation) { dbgFatalf(DBG_Loc, "Heap at 0x%x has invalid validation key of 0x%x", heap, heap->validation); } dbgAssert(heap->allocated.num + heap->free.num == heap->nBlocks); dbgAssert(heap->blockSize == info->blockSize); //dbgAssert(heap->nBlocks == info->nBlocks); #endif if (info->blockSize >= length) { //if this heap has large enough blocks #if MEM_SMALLBLOCK_STATS heap->nAllocationAttempts++; //one more allocation on this heap heap->usage += (real32)heap->allocated.num; #endif if (heap->free.num == 0) { //if the heap is empty nNewCookies = heap->nBlocks * MEM_GrowFactor; #if MEM_VERBOSE_LEVEL >= 1 dbgMessagef("\nmemAllocFunctionSBH: heap of blocks size %d all used up. All %d blocks.\nAllocating another %d", heap->blockSize, heap->nBlocks, nNewCookies); #endif #if MEM_SMALLBLOCK_STATS heap->nOverflowedAllocations++; #endif memSBHGrowBy(heap, nNewCookies); } dbgAssert(heap->free.num > 0); cookie = listGetStructOfNode(heap->free.tail); mbhCookieVerify(cookie); //validate this cookie listRemoveNode(&cookie->link); //remove from free list listAddNode(&heap->allocated, &cookie->link, cookie);//add to allocated list newPointer = (ubyte *)(cookie + 1); #if MEM_CLEAR_MEM //clear the new block to allocated pattern memClearDword(newPointer, memClearSetting, heap->blockSize / sizeof(udword)); #endif bitSet(cookie->flags, MBF_AllocatedNext); //flag block as allocated cookie->length = length; //record length of cookie as allocated #if MEM_LOG_MOSTVOLATILE cookie->timeAllocated = taskTimeElapsed; #endif #if MEM_STATISTICS if (keyIsStuck(MEM_StatsKey) && keyIsHit(CONTROLKEY)) { keyClearSticky(MEM_StatsKey); memStatsLogging ^= TRUE; } memNumberAllocs++; //another walk, another allocation memNumberWalks++; memCookieNameAdd(name, length); #endif mbhNameSet(cookie, name); break; //correct block size found,break out of search loop } } #if MEM_ANALYSIS_KEY if (keyIsHit(SHIFTKEY) && keyIsStuck(MEM_ANALYSIS_KEY)) { keyClearSticky(MEM_ANALYSIS_KEY); memAnalysisCreate(); } #endif return(newPointer); //allocation failed. Too bad } #endif //MEM_SMALL_BLOCK_HEAP /*----------------------------------------------------------------------------- Name : memAllocFunctionANV Description : Allocates a block of RAM Inputs : length - length of requested block in bytes, rounded to a block length name(optional) - name of block flags - allocation control flags pool - pool to allocate from Outputs : Newly allocated block may be cleared. Return : Pointer to newly allocated block, or NULL if failure. ----------------------------------------------------------------------------*/ #if MEM_USE_NAMES void *memAllocFunctionANV(sdword length, char *name, udword flags, mempool *pool) #else void *memAllocFunctionANV(sdword length, udword flags, mempool *pool) #endif { ubyte *newPointer = NULL; memcookie *cookie, *newCookie, *nextCookie; memCookieVerify(pool->lastFree); if (length <= MSB_SizeMax && mslSizeBytes(length) < pool->lastFree) { cookie = mslSizeBytes(length) - mslSizeBytes(length)->blocksPrevious - 1; memCookieVerify(cookie); //make sure cookie is valid } else { cookie = pool->lastFree - pool->lastFree->blocksPrevious - 1; memCookieVerify(cookie); //make sure cookie is valid } while (cookie >= pool->firstFree) { #if MEM_STATISTICS memNumberWalks++; #endif memCookieVerify(cookie); //make sure cookie is valid if (!bitTest(cookie->flags, MBF_AllocatedNext)) //if this block free { if (memBlocksToBytes(cookie->blocksNext) >= length)//if block bigger or equal to size needed { if (memBlocksToBytes(cookie->blocksNext) > length)//if block bigger size { nextCookie = (memcookie *)((ubyte *)cookie +//next cookie sizeof(memcookie) + memBlocksToBytes(cookie->blocksNext)); memCookieVerify(nextCookie); //make sure next cookie valid dbgAssert(nextCookie->blocksPrevious == nextCookie - cookie - 1); nextCookie->blocksPrevious = //next cookie has fewer length / MEM_BlockSize; //bytes previous to it newCookie = (memcookie *)((ubyte *)nextCookie - sizeof(memcookie) - length); newCookie->blocksNext = length / MEM_BlockSize; newCookie->blocksPrevious = cookie->blocksNext - length / MEM_BlockSize - memBytesToBlocks(sizeof(memcookie)); cookie->blocksNext -= length / MEM_BlockSize + 1; /* if (newCookie->blocksNext * MEM_BlockSize <= MSB_SizeMax && mslSizeBlocks(newCookie->blocksNext) - mslSizeBlocks(newCookie->blocksNext)->blocksPrevious - 1 > newCookie) { //set the min size block to applicable value mslSizeBlocks(newCookie->blocksNext) = newCookie + newCookie->blocksNext + 1; } */ #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemAllocFunctionANV: found free block of size %d at 0x%x, made new cookie at 0x%x of size %d", memBlocksToBytes(cookie->blocksNext), cookie, newCookie, memBlocksToBytes(newCookie->blocksNext)); #endif } else { //else block is just the right length #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemAllocFunctionANV: found free same-size block at 0x%x", cookie); #endif newCookie = cookie; } newCookie->flags = MBF_VerifyValue | MBF_AllocatedNext | (flags & MBF_ParameterMask); memNameSet(newCookie, name); #if MEM_DETECT_VOLATILE newCookie->timeAllocated = taskTimeElapsed; //remember when cookie was allocated #endif newPointer = (ubyte *)(newCookie + 1); //set return pointer if (length <= MSB_SizeMax) { //if this was a small block allocation //while this is not the first free block in the specified //size, it'll sure reduce the number of walks a lot. mslSizeBytes(length) = cookie + cookie->blocksNext + 1; } if ((memcookie *)((ubyte *)newCookie + length + sizeof(memcookie)) == pool->lastFree) { //if just allocated the last free cookie nextCookie = newCookie; do { //find new last free cookie pool->lastFree = nextCookie; nextCookie = (memcookie *)((ubyte *)pool->lastFree - sizeof(memcookie) - memBlocksToBytes(pool->lastFree->blocksPrevious)); memCookieVerify(nextCookie); if (nextCookie <= pool->firstFree) { break; } } while (bitTest(nextCookie->flags, MBF_AllocatedNext)); // memNameSet(pool->lastFree, MEM_NameHeapLast); } break; //found free cookie, stop searching } #if MEM_VERBOSE_LEVEL >= 4 dbgMessagef("\nmemAllocFunctionANV: skipping free block of size %d at 0x%x - too small", memBlocksToBytes(cookie->blocksNext), cookie); #endif } else { #if MEM_VERBOSE_LEVEL >= 4 dbgMessagef("\nmemAllocFunctionANV: skipping block of size %d at 0x%x - already allocated", memBlocksToBytes(cookie->blocksNext), cookie); #endif } if (cookie->blocksPrevious == -1) { //if reached bottom of heap break; } dbgAssert(memBlocksToBytes(cookie->blocksPrevious) >= 0 && memBlocksToBytes(cookie->blocksPrevious) < pool->heapLength); cookie = (memcookie *)((ubyte *)cookie - //next cookie sizeof(memcookie) - memBlocksToBytes(cookie->blocksPrevious)); } if (newPointer == NULL) { //if allocation failed if (length > MSB_SizeMax) { //if we were searching from the beginning #if MEM_VERBOSE_LEVEL >= 1 dbgMessagef("\nmemAllocFunctionANV: failed to find block of length %d", length); #endif } else if (mslSizeBytes(length) != pool->lastFree && (mslSizeBytes(length) != pool->lastFree)) { //set this block to last free block and try again #if MEM_VERBOSE_LEVEL >= 1 dbgMessagef("\nmemAllocFunctionANV: failed to find MSL block of length %d", length); #endif //mslSizeBytes(length) = (memcookie *)((ubyte *)pool->lastFree - sizeof(memcookie)); mslSizeBytes(length) = pool->lastFree; #if MEM_USE_NAMES newPointer = memAllocFunctionANV(length, name, flags, pool); //try to allocate again #else newPointer = memAllocFunctionANV(length, flags, pool); #endif return(newPointer); } } else { #if MEM_CLEAR_MEM //clear the new block memClearDword(newPointer, memClearSetting, length / sizeof(udword)); #endif } #if MEM_STATISTICS if (keyIsStuck(MEM_StatsKey) && keyIsHit(CONTROLKEY)) { keyClearSticky(MEM_StatsKey); memStatsLogging ^= TRUE; } memNumberAllocs++; memCookieNameAdd(name, length); #endif #if MEM_ANALYSIS_KEY if (keyIsHit(SHIFTKEY) && keyIsStuck(MEM_ANALYSIS_KEY)) { keyClearSticky(MEM_ANALYSIS_KEY); memAnalysisCreate(); } #endif #if MEM_ANAL_CHECKING memPoolAnalCheck(pool); #endif return(newPointer); } /*----------------------------------------------------------------------------- Name : memAllocFunctionA Description : Allocates a block of volatile RAM, that is a block to be freed soon. Inputs : length - length of requested block in bytes name(optional) - name of block flags - allocation control flags pool - pool to allocate from Outputs : Newly allocated block may be cleared. Return : Pointer to newly allocated block, or NULL if failure. ----------------------------------------------------------------------------*/ #if MEM_USE_NAMES void *memAllocFunctionA(sdword length, char *name, udword flags, mempool *pool) #else void *memAllocFunctionA(sdword length, udword flags, mempool *pool) #endif { ubyte *newPointer = NULL; memcookie *cookie, *newCookie, *nextCookie; memInitCheck(); dbgAssert(length > 0/* && length < pool->heapLength*/); //verify size is reasonable #if MEM_VERBOSE_LEVEL >= 2 dbgMessagef("\nmemAllocFunctionA: allocating %d bytes for '%s'", length, name); #endif //small block heap allocation scheme, if applicable #if MEM_SMALL_BLOCK_HEAP if (bitTest(flags, MBF_SmallBlockHeap) && pool == &memMainPool) { #if MEM_USE_NAMES cookie = memAllocFunctionSBH(length, name, flags); #else cookie = memAllocFunctionSBH(length, flags); #endif if (cookie != NULL) { //if small block allocation worked return(cookie); } //else fall through to the standard allocation scheme bitClear(flags, MBF_SmallBlockHeap); } #endif length = memRoundUp(length); //round length up to block size //nonvolatile allocation scheme, if applicable if (bitTest(flags, MBF_NonVolatile)) { #if MEM_USE_NAMES return(memAllocFunctionANV(length, name, flags, pool)); #else return(memAllocFunctionANV(length, flags, pool)); #endif } //regular volatile allocation, choose small or large blocks if (length > MSB_SizeMax) { cookie = pool->firstFree; //start at beginning of free list for large allocations } else { cookie = max(pool->firstFree, msbSizeBytes(length)); //start at first free block of a given size } while (cookie < pool->lastFree) { //walk through entire list #if MEM_STATISTICS memNumberWalks++; #endif memCookieVerify(cookie); //make sure cookie is valid if (!bitTest(cookie->flags, MBF_AllocatedNext)) //if this block free { if (memBlocksToBytes(cookie->blocksNext) >= length)//if block bigger or equal { if (memBlocksToBytes(cookie->blocksNext) > length)//if block bigger size { nextCookie = (memcookie *)((ubyte *)cookie +//next cookie sizeof(memcookie) + memBlocksToBytes(cookie->blocksNext)); memCookieVerify(nextCookie); //make sure next cookie valid dbgAssert(nextCookie->blocksPrevious == nextCookie - cookie - 1); nextCookie->blocksPrevious -= //next cookie has fewer length / MEM_BlockSize + 1; //bytes previous to it newCookie = (memcookie *)((ubyte *)cookie + sizeof(memcookie) + length); newCookie->flags = MBF_VerifyValue; //init the new cookie newCookie->blocksNext = cookie->blocksNext - length / MEM_BlockSize - memBytesToBlocks(sizeof(memcookie)); newCookie->blocksPrevious = length / MEM_BlockSize; memNameSet(newCookie, MEM_HeapFree); //set the name of new cookie //set min size pointer for newly created block if applicable /* if (newCookie->blocksNext * MEM_BlockSize <= MSB_SizeMax && msbSizeBlocks(newCookie->blocksNext) < newCookie) { //set the min size block to applicable value msbSizeBlocks(newCookie->blocksNext) = newCookie; } */ #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemAllocFunctionA: found free block of size %d at 0x%x, made new cookie at 0x%x of size %d", memBlocksToBytes(cookie->blocksNext), cookie, newCookie, memBlocksToBytes(newCookie->blocksNext)); #endif cookie->blocksNext = length / MEM_BlockSize;//set new block size } else { #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemAllocFunctionA: found free same-size block at 0x%x", cookie); #endif } memNameSet(cookie, name); //set new name cookie->flags = MBF_VerifyValue | MBF_AllocatedNext | (flags & MBF_ParameterMask); #if MEM_DETECT_VOLATILE cookie->timeAllocated = taskTimeElapsed; //remember when cookie was allocated #endif newPointer = (ubyte *)(cookie + 1); //set return pointer if (length <= MSB_SizeMax) { //if this was a small block allocation //while this is not the first free block in the specified //size, it'll sure reduce the number of walks a lot. msbSizeBytes(length) = cookie; } if (cookie == pool->firstFree) //if allocating first free block { //find next free cookie while (bitTest(pool->firstFree->flags, MBF_AllocatedNext)) { memCookieVerify(pool->firstFree); //verify this cookie nextCookie = (memcookie *)((ubyte *)pool->firstFree +//next cookie sizeof(memcookie) + memBlocksToBytes(pool->firstFree->blocksNext)); if (nextCookie >= pool->lastFree) //if cookie pointed past end of heap { goto noMoreFree; //no more free structures } pool->firstFree = nextCookie; } memNameSet(pool->firstFree, MEM_NameHeapFirst); noMoreFree:; } break; } #if MEM_VERBOSE_LEVEL >= 4 dbgMessagef("\nmemAllocFunctionA: skipping free block of size %d at 0x%x - too small", memBlocksToBytes(cookie->blocksNext), cookie); #endif } else { #if MEM_VERBOSE_LEVEL >= 4 dbgMessagef("\nmemAllocFunctionA: skipping block of size %d at 0x%x - already allocated", memBlocksToBytes(cookie->blocksNext), cookie); #endif } dbgAssert(memBlocksToBytes(cookie->blocksNext) >= 0 && memBlocksToBytes(cookie->blocksNext) < pool->heapLength); cookie = (memcookie *)((ubyte *)cookie + //next cookie sizeof(memcookie) + memBlocksToBytes(cookie->blocksNext)); } if (newPointer == NULL) { //if allocation failed if (length > MSB_SizeMax) { //if we were searching from the beginning #if MEM_VERBOSE_LEVEL >= 1 dbgMessagef("\nmemAllocFunctionA: failed to find block of length %d", length); #endif } else if (msbSizeBytes(length) != pool->firstFree && (msbSizeBytes(length) != pool->firstFree)) { //failure may be due to small-block fragmentation #if MEM_VERBOSE_LEVEL >= 1 dbgMessagef("\nmemAllocFunctionA: failed to find MSB block of length %d", length); #endif msbSizeBytes(length) = pool->firstFree; //set this block to first free block #if MEM_USE_NAMES newPointer = memAllocFunctionA(length, name, flags, pool); //try to allocate again #else newPointer = memAllocFunctionA(length, flags, pool); #endif return(newPointer); } } else { #if MEM_CLEAR_MEM //clear the new block memClearDword(newPointer, memClearSetting, length / sizeof(udword)); #endif } #if MEM_STATISTICS if (keyIsStuck(MEM_StatsKey) && keyIsHit(CONTROLKEY)) { keyClearSticky(MEM_StatsKey); memStatsLogging ^= TRUE; } memNumberAllocs++; memCookieNameAdd(name, length); #endif #if MEM_ANALYSIS_KEY if (keyIsHit(SHIFTKEY) && keyIsStuck(MEM_ANALYSIS_KEY)) { keyClearSticky(MEM_ANALYSIS_KEY); memAnalysisCreate(); } #endif #if MEM_ANAL_CHECKING memPoolAnalCheck(pool); #endif return(newPointer); } /*----------------------------------------------------------------------------- Name : memNewGrowthPoolAlloc Description : Allocate and initialize a new growth heap pool. Inputs : length - length of block we're trying to allocate in the new heap Outputs : Return : newly allocated pool. Will create fatal error if it cannot allocate new pool. ----------------------------------------------------------------------------*/ mempool *memNewGrowthPoolAlloc(sdword length) { void *newPool; //this error checking shall be present in release builds if (memNumberGrowthPools >= MGH_NumberGrowthHeaps) { dbgFatalf(DBG_Loc, "Exceeded %d growth heaps, each of length at least %d.", MGH_NumberGrowthHeaps, MGH_MinGrowthSize); } length = max(length, MGH_MinGrowthSize); //larger of growth size and cookie length += sizeof(memcookie) * 4; //a cookie high and low: that should be a heap large enough for everyone #if MEM_VERBOSE_LEVEL dbgMessagef("\nAllocating growth heap %d of size %d", memNumberGrowthPools, length); #endif newPool = memGrowthAllocate(length); if (newPool == NULL) { dbgFatalf(DBG_Loc, "Could not allocate growth heap %d with a size of %d", memNumberGrowthPools, length); } memPoolReset(&memGrowthPool[memNumberGrowthPools], newPool, length, FALSE); memNumberGrowthPools++; return(&memGrowthPool[memNumberGrowthPools - 1]); } /*----------------------------------------------------------------------------- Name : memAllocAttemptFunction Description : Attempt to allocate memory, returning NULL if there's not for sale. Inputs : length - length of requested block in bytes name(optional) - name of block flags - allocation control flags Outputs : Newly allocated block may be cleared. Return : New pointer or NULL on failure. ----------------------------------------------------------------------------*/ #if MEM_USE_NAMES void *memAllocAttemptFunction(sdword length, char *name, udword flags) #else void *memAllocAttemptFunction(sdword length, udword flags) #endif { sdword iPool; mempool *pool; ubyte *newPointer = NULL; #if MEM_ERROR_CHECKING if (length <= 0) { dbgFatalf(DBG_Loc, "Attempted to allocate %d bytes of '%s'", length, name); } #endif if (!bitTest(flags, MBF_ExtendedPoolOnly)) { #if MEM_USE_NAMES newPointer = memAllocFunctionA(length, name, flags, &memMainPool);//call the main allocation routine #else newPointer = memAllocFunctionA(length, flags, &memMainPool); //call the main allocation routine #endif #if MEM_STATISTICS memAllocPool++; #endif } if (newPointer == NULL) { //no RAM left in the main heap bitClear(flags, MBF_SmallBlockHeap); //no SBH's in growth heaps for (iPool = 0, pool = memGrowthPool; iPool < memNumberGrowthPools; iPool++, pool++) { //try to alloc from currently active growth pools #if MEM_STATISTICS memNumberAllocs--; //the last allocation failed memAllocPool++; #endif #if MEM_USE_NAMES newPointer = memAllocFunctionA(length, name, flags, pool); #else newPointer = memAllocFunctionA(length, flags, pool); //call the main allocation routine for this pool #endif if (newPointer != NULL) { //if allocated from growth pool return(newPointer); } } //failed to allocate from existing growth pools pool = memNewGrowthPoolAlloc(length); //allocate a new growth pool #if MEM_USE_NAMES newPointer = memAllocFunctionA(length, name, flags, pool); #else newPointer = memAllocFunctionA(length, flags, pool); //call the main allocation routine #endif } return(newPointer); } /*----------------------------------------------------------------------------- Name : memAllocFunction Description : Allocates a block of RAM Inputs : length - length of requested block in bytes name(optional) - name of block flags - allocation control flags Outputs : Newly allocated block may be cleared. Return : Pointer to newly allocated block. This function will not return NULL on allocation failure, rather it will generate a fatal error. ----------------------------------------------------------------------------*/ #if MEM_USE_NAMES void *memAllocFunction(sdword length, char *name, udword flags) #else void *memAllocFunction(sdword length, udword flags) #endif { ubyte *newPointer = NULL; sdword iPool; mempool *pool; #if MEM_ERROR_CHECKING if (length <= 0) { dbgFatalf(DBG_Loc, "Attempted to allocate %d bytes of '%s'", length, name); } #endif if (!bitTest(flags, MBF_ExtendedPoolOnly)) { #if MEM_USE_NAMES newPointer = memAllocFunctionA(length, name, flags, &memMainPool);//call the main allocation routine #else newPointer = memAllocFunctionA(length, flags, &memMainPool); //call the main allocation routine #endif #if MEM_STATISTICS memAllocPool++; #endif } if (newPointer == NULL) //if failure { bitClear(flags, MBF_SmallBlockHeap); //no SBH's in growth heaps for (iPool = 0, pool = memGrowthPool + memNumberGrowthPools - 1; iPool < memNumberGrowthPools; iPool++, pool--) { //try to alloc from currently active growth pools #if MEM_STATISTICS memNumberAllocs--; //the last allocation failed memAllocPool++; #endif #if MEM_USE_NAMES newPointer = memAllocFunctionA(length, name, flags, pool); #else newPointer = memAllocFunctionA(length, flags, pool); //call the main allocation routine for this pool #endif if (newPointer != NULL) { //if allocated from growth pool return(newPointer); } } //failed to allocate from existing growth pools pool = memNewGrowthPoolAlloc(length); //allocate a new growth pool #if MEM_USE_NAMES newPointer = memAllocFunctionA(length, name, flags, pool); #else newPointer = memAllocFunctionA(length, flags, pool); //call the main allocation routine #endif if (newPointer == NULL) { #if MEM_ANALYSIS_AUTOCREATE memAnalysisCreate(); #if MEM_USE_NAMES dbgFatalf(DBG_Loc, "memAlloc: failed to allocate %d bytes of %s. Analysis saved.", length, name); #else dbgFatalf(DBG_Loc, "memAlloc: failed to allocate %d bytes. Analysis saved.", length); #endif //MEM_USE_NAMES #else //MEM_ANALYSIS_AUTOCREATE #if MEM_USE_NAMES dbgFatalf(DBG_Loc, "memAlloc: failed to allocate %d bytes of %s.", length, name); #else dbgFatalf(DBG_Loc, "memAlloc: failed to allocate %d bytes.", length); #endif //MEM_USE_NAMES #endif //MEM_ANALYSIS_AUTOCREATE } } return(newPointer); } /*----------------------------------------------------------------------------- Name : memVolatilityLogFuntion Description : Log the volatility of a cookie being freed Inputs : cookie - cookie to gather stats on Outputs : saves the amount of time this beatch was allocated to memVolatileStat Return : void ----------------------------------------------------------------------------*/ #if MEM_LOG_MOSTVOLATILE void memVolatilityLogFuntion(memcookie *cookie) { sdword index; if (bitTest(cookie->flags, MBF_String)) { return; //don't log strings } for (index = 0; index < MEM_NumberVolatileStats; index++) { //search through stats array if (memVolatileStat[index].nLogged == 0.0f) { //if unused slot memcpy(memVolatileStat[index].name, cookie->name, MEM_NameLength); memVolatileStat[index].nLogged = 1.0f; memVolatileStat[index].totalTime = taskTimeElapsed - cookie->timeAllocated; return; } else { //else something in slot if (!strcmp(memVolatileStat[index].name, cookie->name)) { //if this is the stats slot for this cookie memVolatileStat[index].nLogged += 1.0f; //update already started stat memVolatileStat[index].totalTime += taskTimeElapsed - cookie->timeAllocated; return; } } } dbgMessagef("\nCannot log volatility of '%s': stats array length %d full", cookie->name, MEM_NumberVolatileStats); } int __cdecl memVolatilitySort(const void *p0, const void *p1) { real32 diff; diff = ((volatilestat *)p0)->volatility - ((volatilestat *)p1)->volatility; if (diff < 0) { return(-1); } else if (diff > 0) { return(1); } else { return(0); } } #endif /*----------------------------------------------------------------------------- Name : memFreeSBH Description : Free a block of memory allocated from a small block heap. Inputs : cookie - cookie we are freeing Outputs : Return : void ----------------------------------------------------------------------------*/ #if MEM_SMALL_BLOCK_HEAP void memFreeSBH(mbhcookie *cookie) { mbheap *heap; dbgAssert(bitTest(cookie->flags, MBF_AllocatedNext)); heap = (mbheap*)(((ubyte *)cookie->link.belongto) - MBH_AllocatedLinkOffset); #if MEM_ERROR_CHECKING dbgAssert(heap->validation == MEM_HeapValidation); #endif listRemoveNode(&cookie->link); listAddNode(&heap->free, &cookie->link, cookie); bitClear(cookie->flags, MBF_AllocatedNext); //flag the cookie as free mbhNameSet(cookie, MEM_HeapFree); //name the cookie is free dbgAssert(heap->free.num + heap->allocated.num == heap->nBlocks); } #endif /*----------------------------------------------------------------------------- Name : memFreeNV Description : Free a non-volatile memory cookie (only to be called from memFree) Inputs : cookie - cookie to free pool - pool to free from Outputs : Return : ----------------------------------------------------------------------------*/ void memFreeNV(memcookie *cookie, memcookie *nextCookie, mempool *pool) { memcookie *previousCookie; memcookie *stillNextCookie; udword length, index; if (nextCookie < pool->last) //if not end of heap { memCookieVerify(nextCookie); //make sure cookie is valid if (!bitTest(nextCookie->flags, MBF_AllocatedNext)) //if next block also free { #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemFree: combined blocks 0x%x(%d) and 0x%x(%d) into one.", cookie, memBlocksToBytes(cookie->blocksNext), nextCookie, memBlocksToBytes(nextCookie->blocksNext)); #endif //combine sizes stillNextCookie = nextCookie + nextCookie->blocksNext + 1; memCookieVerify(stillNextCookie); cookie->blocksNext += nextCookie->blocksNext + MEM_BlocksPerCookie; stillNextCookie->blocksPrevious = cookie->blocksNext; //make sure that no min size pointers are referencing lost cookie for (index = 0; index < MSB_NumberSizes + 1; index++) { if (pool->firstSize[index] == nextCookie) { pool->firstSize[index] = cookie; } if (pool->lastSize[index] == nextCookie) { pool->lastSize[index] = cookie + cookie->blocksNext + 1; memCookieVerify(pool->lastSize[index]); } } #if MEM_CLEAR_MEM //clear lost cookie memClearDword(nextCookie, memFreeSetting, sizeof(memcookie) / sizeof(udword)); #endif } } //attempt to combine with previous cookie if both free if (cookie->blocksPrevious != -1) { //if there is a previous block previousCookie = (memcookie *)((ubyte *)cookie - //get previous block sizeof(memcookie) - memBlocksToBytes(cookie->blocksPrevious)); dbgAssert(previousCookie >= pool->first); memCookieVerify(previousCookie); if (!bitTest(previousCookie->flags, MBF_AllocatedNext)) //if previous block also free { #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemFree: combined blocks 0x%x(%d) and 0x%x(%d) into one.", previousCookie, memBlocksToBytes(previousCookie->blocksNext), cookie, memBlocksToBytes(cookie->blocksNext)); #endif //combine sizes nextCookie = cookie + cookie->blocksNext + 1; memCookieVerify(nextCookie); nextCookie->blocksPrevious += cookie->blocksPrevious + 1; previousCookie->blocksNext = nextCookie->blocksPrevious; #if MEM_CLEAR_MEM //clear lost cookie memClearDword(cookie, memFreeSetting, sizeof(memcookie) / sizeof(udword)); #endif //make sure that no pointers are referencing lost cookie for (index = 0; index < MSB_NumberSizes + 1; index++) { //check all the min size pointers if (pool->firstSize[index] == cookie) { pool->firstSize[index] = previousCookie; } if (pool->lastSize[index] == cookie) { pool->lastSize[index] = previousCookie + previousCookie->blocksNext + 1; memCookieVerify(pool->lastSize[index]); } } if (pool->lastFree == cookie) { //check the last free pointer pool->lastFree = nextCookie; } cookie = previousCookie; //make sure that the later resizing will work } else { //else prev cookie in use length = cookie->blocksNext * MEM_BlockSize; if (length <= MSB_SizeMax && mslSizeBytes(length) < cookie + cookie->blocksNext + 1) { //set the new size block max mslSizeBytes(length) = cookie + cookie->blocksNext + 1; } } } if (pool->lastFree <= cookie) { pool->lastFree = (memcookie *)((ubyte *)cookie + sizeof(memcookie) + memBlocksToBytes(cookie->blocksNext)); dbgAssert(pool->lastFree > pool->firstFree && pool->lastFree <= pool->last); memCookieVerify(pool->lastFree); } #if MEM_ANAL_CHECKING memPoolAnalCheck(pool); #endif } /*----------------------------------------------------------------------------- Name : memFree Description : Frees a block of memory Inputs : pointer - pointer to memory to be freed Outputs : Return : void ----------------------------------------------------------------------------*/ void memFree(void *pointer) { memcookie *cookie, *nextCookie, *previousCookie; memcookie *stillNextCookie; udword length, index; mempool *pool = &memMainPool; //what pool does this cookie belong to? memInitCheck(); dbgAssert(pointer != NULL); cookie = (memcookie *)pointer; cookie--; //get pointer to cookie structure memCookieVerify(cookie); //make sure cookie is valid if (pointer < memMainPool.pool || pointer > (void *)memMainPool.last) { //if it's not part of the main pool for (index = 0, pool = memGrowthPool; index < memNumberGrowthPools; index++, pool++) { //search all existing pools if (pointer > pool->pool && pointer <= (void *)pool->last) { goto foundPool; } } #if MEM_ERROR_CHECKING dbgFatalf(DBG_Loc, "Cound not find pool to which pointer 0x%x belongs.", pointer); #endif } foundPool:; #if MEM_ANAL_CHECKING memPoolAnalCheck(pool); #endif //see if it's a small block heap cookie #if MEM_SMALL_BLOCK_HEAP if (bitTest(cookie->flags, MBF_SmallBlockHeap)) { memVolatilityLog(cookie); //dbgAssert(pointer >= memMainPool.pool && pointer <= memMainPool.last);//SBH allocations can only be in the main pool memFreeSBH((mbhcookie *)cookie); #if MEM_ANAL_CHECKING memPoolAnalCheck(pool); #endif return; } #endif //MEM_SMALL_BLOCK_HEAP memVolatilityLog(cookie); #if MEM_DETECT_VOLATILE if (taskTimeElapsed - cookie->timeAllocated > MEM_VolatileLong) { //if cookie not volatile if (!bitTest(cookie->flags, MBF_NonVolatile)) { #if MEM_DEBUG_NV dbgMessagef("\nmemFree: cookie 0x%x ('%s') is NON-volatile.", cookie, cookie->name); #endif #if MEM_FILE_NV if (memNonVolatileFile) { fprintf(memNonVolatileFile, "memFree: cookie 0x%x ('%s') is NON-volatile.\n", cookie, cookie->name); } #endif //MEM_FILE_NV } } else if (taskTimeElapsed - cookie->timeAllocated < MEM_VolatileShort) { //else cookie is volatile if (bitTest(cookie->flags, MBF_NonVolatile)) { //and supposedto be non-volatile #if MEM_DEBUG_NV dbgMessagef("\nmemFree: cookie 0x%x ('%s') is volatile.", cookie, cookie->name); #endif #if MEM_FILE_NV if (memNonVolatileFile) { fprintf(memNonVolatileFile, "memFree: cookie 0x%x ('%s') is volatile.\n", cookie, cookie->name); } #endif //MEM_FILE_NV } } #endif //MEM_DETECT_VOLATILE #if MEM_ERROR_CHECKING dbgAssert(cookie->blocksNext > 0); //ensure non-zero size dbgAssert((ubyte *)cookie + sizeof(memcookie) + memBlocksToBytes(cookie->blocksNext) <= (ubyte *)pool->last); #endif #if MEM_VERBOSE_LEVEL >= 2 dbgMessagef("\nmemFree: freed %d bytes of '%s' from 0x%x", memBlocksToBytes(cookie->blocksNext), cookie->name, cookie); #endif bitClear(cookie->flags, MBF_AllocatedNext); //say it's not active memNameSet(cookie, MEM_HeapFree); //clear name of cookie //clear actual memory block #if MEM_CLEAR_MEM_ON_FREE memClearDword(pointer, memFreeSetting, memBlocksToBytes(cookie->blocksNext) / 4); #endif nextCookie = (memcookie *)((ubyte *)cookie + //next cookie sizeof(memcookie) + memBlocksToBytes(cookie->blocksNext)); if (bitTest(cookie->flags, MBF_NonVolatile)) { //if allocated non-volatile memFreeNV(cookie, nextCookie, pool); #if MEM_ANAL_CHECKING memPoolAnalCheck(pool); #endif return; } if (nextCookie < pool->last) //if not end of heap { memCookieVerify(nextCookie); //make sure cookie is valid if (!bitTest(nextCookie->flags, MBF_AllocatedNext)) //if next block also free { #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemFree: combined blocks 0x%x(%d) and 0x%x(%d) into one.", cookie, memBlocksToBytes(cookie->blocksNext), nextCookie, memBlocksToBytes(nextCookie->blocksNext)); #endif //combine sizes stillNextCookie = nextCookie + nextCookie->blocksNext + 1; memCookieVerify(stillNextCookie); cookie->blocksNext += nextCookie->blocksNext + MEM_BlocksPerCookie; stillNextCookie->blocksPrevious = cookie->blocksNext; //make sure that no min size pointers are referencing lost cookie for (index = 0; index < MSB_NumberSizes + 1; index++) { if (pool->firstSize[index] == nextCookie) { pool->firstSize[index] = cookie; } if (pool->lastSize[index] == nextCookie) { pool->lastSize[index] = stillNextCookie; } } #if MEM_CLEAR_MEM //clear lost cookie memClearDword(nextCookie, memFreeSetting, sizeof(memcookie) / sizeof(udword)); #endif } } //attempt to combine with previous cookie if both free if (cookie->blocksPrevious != -1) { //if there is a previous block previousCookie = (memcookie *)((ubyte *)cookie - //get previous block sizeof(memcookie) - memBlocksToBytes(cookie->blocksPrevious)); dbgAssert(previousCookie >= pool->first); memCookieVerify(previousCookie); if (!bitTest(previousCookie->flags, MBF_AllocatedNext)) //if previous block also free { #if MEM_VERBOSE_LEVEL >= 3 dbgMessagef("\nmemFree: combined blocks 0x%x(%d) and 0x%x(%d) into one.", previousCookie, memBlocksToBytes(previousCookie->blocksNext), cookie, memBlocksToBytes(cookie->blocksNext)); #endif //combine sizes nextCookie = cookie + cookie->blocksNext + 1; memCookieVerify(nextCookie); nextCookie->blocksPrevious += cookie->blocksPrevious + 1; previousCookie->blocksNext = nextCookie->blocksPrevious; #if MEM_CLEAR_MEM //clear lost cookie memClearDword(cookie, memFreeSetting, sizeof(memcookie) / sizeof(udword)); #endif //make sure that no pointers are referencing lost cookie for (index = 0; index < MSB_NumberSizes + 1; index++) { //check all the min size pointers if (pool->firstSize[index] == cookie) { pool->firstSize[index] = previousCookie; } if (pool->lastSize[index] == cookie) { pool->lastSize[index] = nextCookie; } } if (pool->lastFree == cookie) { //check the last free pointer pool->lastFree = nextCookie; } cookie = previousCookie; //make sure that the later resizing will work } else { length = cookie->blocksNext * MEM_BlockSize; if (length <= MSB_SizeMax && msbSizeBytes(length) > cookie) { //set the new size block min msbSizeBytes(length) = cookie; } } } if (pool->firstFree > cookie) //make sure first free is the actual first free { pool->firstFree = cookie; } dbgAssert(pool->lastFree > pool->firstFree && pool->lastFree <= pool->last); #if MEM_ANAL_CHECKING memPoolAnalCheck(pool); #endif } /*----------------------------------------------------------------------------- Name : memStringDupe Description : Duplicate a string Inputs : string - pointer to string to duplicate Outputs : allocates and strcpy's the string Return : newly allocated string Note : Use memFree to free these strings ----------------------------------------------------------------------------*/ char *memStringDupe(char *string) { char *newString; newString = memAlloc(strlen(string) + 1, string, MBF_String); strcpy(newString, string); return(newString); } /*----------------------------------------------------------------------------- Name : memStringDupeNV Description : Duplicate a string into non-volatile RAM Inputs : string - pointer to string to duplicate Outputs : allocates and strcpy's the string Return : newly allocated string Note : Use memFree to free these strings ----------------------------------------------------------------------------*/ char *memStringDupeNV(char *string) { char *newString; newString = memAlloc(strlen(string) + 1, string, MBF_String); strcpy(newString, string); return(newString); } /*----------------------------------------------------------------------------- Name : memReallocFunction Description : Grow or shrink a block of memory. Inputs : currentPointer - currently allocated block, or NULL for no block. newSize - the new size to be sized to name - new name flags - new allocation flags Outputs : Return : newly allocated or resized block. Note : This function will always re-allocate the block. This can be optimized. ----------------------------------------------------------------------------*/ #if MEM_USE_NAMES void *memReallocFunction(void *currentPointer, sdword newSize, char *name, udword flags) #else void *memReallocFunction(void *currentPointer, sdword newSize, udword flags) #endif { void *newPointer; memcookie *cookie; sdword oldLength; if (currentPointer == NULL) { newPointer = memAlloc(newSize, name, flags) return(newPointer); } newPointer = memAlloc(newSize, name, flags); cookie = (memcookie *)((ubyte *)currentPointer - sizeof(memcookie)); #if MEM_SMALL_BLOCK_HEAP if (bitTest(cookie->flags, MBF_SmallBlockHeap)) { oldLength = ((mbhcookie*)cookie)->length; } else { #endif oldLength = memBlocksToBytes(cookie->blocksNext); #if MEM_SMALL_BLOCK_HEAP } #endif memcpy(newPointer, currentPointer, min(newSize, oldLength)); memFree(currentPointer); return(newPointer); } /*----------------------------------------------------------------------------- Name : memFreeMemGet Description : Compute size of all available memory. Inputs : pool - what pool to get the stats on Outputs : .. Return : available memory, in bytes ----------------------------------------------------------------------------*/ #if MEM_ANALYSIS sdword memFreeMemGet(mempool *pool) { memcookie *thisCookie, *nextCookie; sdword freeMem = 0; thisCookie = pool->first; //start walk from very start of heap while (thisCookie < pool->last) //scan for free cookies in heap { memCookieVerify(thisCookie); //verify this cookie if (!bitTest(thisCookie->flags, MBF_AllocatedNext)) { freeMem += memBlocksToBytes(thisCookie->blocksNext); } nextCookie = (memcookie *)((ubyte *)thisCookie + //next cookie sizeof(memcookie) + memBlocksToBytes(thisCookie->blocksNext)); if (nextCookie >= pool->last) //if cookie pointed past end of heap { goto noMoreFree; //no more free structures } thisCookie = nextCookie; //next update } noMoreFree:; return freeMem; } #endif /*----------------------------------------------------------------------------- Name : memUsedMemGet Description : Compute amount of memory used Inputs : pool - what pool to get the stats on Outputs : .. Return : Amount of used memory, in bytes. ----------------------------------------------------------------------------*/ #if MEM_ANALYSIS sdword memUsedMemGet(mempool *pool) { memcookie *thisCookie, *nextCookie; sdword usedMem = 0; thisCookie = pool->first; //start walk from very start of heap while (thisCookie < pool->last) //scan for free cookies in heap { memCookieVerify(thisCookie); //verify this cookie if (bitTest(thisCookie->flags, MBF_AllocatedNext)) { usedMem += memBlocksToBytes(thisCookie->blocksNext); } nextCookie = (memcookie *)((ubyte *)thisCookie + //next cookie sizeof(memcookie) + memBlocksToBytes(thisCookie->blocksNext)); if (nextCookie >= pool->last) //if cookie pointed past end of heap { goto noMoreFree; //no more free structures } thisCookie = nextCookie; //next update } noMoreFree:; return usedMem; } #endif /*----------------------------------------------------------------------------- Name : memCookiesLengthGet Description : Compute amount of memory used for cookies Inputs : pool - what pool to get the stats on Outputs : .. Return : Amount of memory used for cookies, in bytes. ----------------------------------------------------------------------------*/ #if MEM_ANALYSIS sdword memCookiesLengthGet(mempool *pool, sdword *nCookies) { memcookie *thisCookie, *nextCookie; sdword usedMem = *nCookies = 0; thisCookie = pool->first; //start walk from very start of heap while (thisCookie < pool->last) //scan for free cookies in heap { memCookieVerify(thisCookie); //verify this cookie usedMem += sizeof(memcookie); nextCookie = (memcookie *)((ubyte *)thisCookie + //next cookie sizeof(memcookie) + memBlocksToBytes(thisCookie->blocksNext)); (*nCookies)++; if (nextCookie >= pool->last) //if cookie pointed past end of heap { goto noMoreFree; //no more free structures } thisCookie = nextCookie; //next update } noMoreFree:; return usedMem; } #endif /*----------------------------------------------------------------------------- Name : memLargestBlockGet Description : Returns largets block size. Inputs : pool - what pool to get the stats on Outputs : .. Return : Size of largest free memory block, in bytes. ----------------------------------------------------------------------------*/ #if MEM_ANALYSIS sdword memLargestBlockGet(mempool *pool) { memcookie *thisCookie, *nextCookie; sdword largestSize = 0; thisCookie = pool->first; //start walk from very start of heap while (thisCookie < pool->last) //scan for free cookies in heap { memCookieVerify(thisCookie); //verify this cookie if (!bitTest(thisCookie->flags, MBF_AllocatedNext)) { largestSize = max(largestSize, memBlocksToBytes(thisCookie->blocksNext)); } nextCookie = (memcookie *)((ubyte *)thisCookie + //next cookie sizeof(memcookie) + memBlocksToBytes(thisCookie->blocksNext)); if (nextCookie >= pool->last) //if cookie pointed past end of heap { goto noMoreFree; //no more free structures } thisCookie = nextCookie; //next update } noMoreFree:; return largestSize; } #endif #if MEM_ANALYSIS /*----------------------------------------------------------------------------- Name : memAnalysisCreateForPool Description : Creates and prints a detailed analysis of the current state of the memory heap. Inputs : pool - what pool to log the stats on fpAnalysis - the analysis file pointer (mem.analysis) fpMap - the map file pointer (mem.map) Outputs : Calls the previous 3 memory computation functions, then saves a detailed memory map to disk. Return : ----------------------------------------------------------------------------*/ #define MEM_ANALYSIS_FILE_NAME "mem.analysis" #define MEM_MAP_FILE_NAME "mem.map" #define MEM_MAP_BLOCKS_PER_LINE 80 #define MEM_MAP_CHAR(c) fputc((c), fpMap); mapColumn++; if (mapColumn >= MEM_MAP_BLOCKS_PER_LINE) {fputc('\n', fpMap);mapColumn = 0;} void memAnalysisCreateForPool(mempool *pool, FILE *fpAnalysis, FILE *fpMap) { sdword largeBlock, freeMem, usedMem, cookieMem, wastedMem, nCookies, index; memcookie *thisCookie, *nextCookie; sdword mapColumn = 0, block; #if MEM_SMALL_BLOCK_HEAP Node *cookieLink; mbheap *heap; mbhcookie *bCookie; #endif dbgMessagef("\nHeap length = %d", pool->heapLength); fprintf(fpAnalysis, "Heap length = %d\n", pool->heapLength); largeBlock = memLargestBlockGet(pool); dbgMessagef("\nLargest block = %d (%.2f%%)", largeBlock, (real32)largeBlock / (real32)pool->heapLength * 100.0f); fprintf(fpAnalysis, "Largest block = %d (%.2f%%)\n", largeBlock, (real32)largeBlock / (real32)pool->heapLength * 100.0f); usedMem = memUsedMemGet(pool); dbgMessagef("\nUsed memory = %d (%.2f%%)", usedMem, (real32)usedMem / (real32)pool->heapLength * 100.0f); fprintf(fpAnalysis, "Used memory = %d (%.2f%%)\n", usedMem, (real32)usedMem / (real32)pool->heapLength * 100.0f); freeMem = memFreeMemGet(pool); dbgMessagef("\nFree memory = %d (%.2f%%)", freeMem, (real32)freeMem / (real32)pool->heapLength * 100.0f); fprintf(fpAnalysis, "Free memory = %d (%.2f%%)\n", freeMem, (real32)freeMem / (real32)pool->heapLength * 100.0f); cookieMem = memCookiesLengthGet(pool, &nCookies); dbgMessagef("\nCookie memory = %d (%.2f%%), %d cookies", cookieMem, (real32)cookieMem / (real32)pool->heapLength * 100.0f, nCookies); fprintf(fpAnalysis, "Cookie memory = %d (%.2f%%), %d cookies\n", cookieMem, (real32)cookieMem / (real32)pool->heapLength * 100.0f, nCookies); wastedMem = pool->heapLength - (usedMem + freeMem + cookieMem); if (wastedMem > 0) { dbgMessagef("\nLost memory = %d (%.2f%%)", wastedMem, (real32)wastedMem / (real32)pool->heapLength * 100.0f); fprintf(fpAnalysis, "Lost memory = %d (%.2f%%)\n", wastedMem, (real32)wastedMem / (real32)pool->heapLength * 100.0f); } fprintf(fpAnalysis, "Cookie Name\t\tCookie length/maxLength\tCookie status\n"); fprintf(fpMap, "Legend: @ = smallBlockHeap, * = cookie, # = allocated, . = free. Block length = %d bytes\n", MEM_BlockSize); #if MEM_SMALL_BLOCK_HEAP //walk through small block heaps and print contents if (pool == &memMainPool) { for (index = 0, heap = (mbheap *)pool->pool; memSmallHeapInfo[index].blockSize > 0; index++, heap++) { //for all heaps //dbgAssert(heap->nBlocks == memSmallHeapInfo[index].nBlocks); fprintf(fpAnalysis, "Heap 0x%x, length = %d\n", heap, heap->nBlocks * heap->blockSize); #if MEM_SMALLBLOCK_STATS if (heap->nAllocationAttempts == 0) { fprintf(fpAnalysis, "avg. usage = %.2f%%, overfull = %.2f%%, allocs/sec = %.2f\n", 0.0f, 0.0f, 0.0f); } else { fprintf(fpAnalysis, "avg. usage = %.2f%%, overfull = %.2f%%, allocs/sec = %.2f\n", heap->usage / (real32)heap->nBlocks / (real32)heap->nAllocationAttempts * 100.0f, (real32)heap->nOverflowedAllocations / (real32)heap->nAllocationAttempts * 100.0f, (real32)heap->nAllocationAttempts / (taskTimeElapsed - heap->firstAllocTime)); } heap->nAllocationAttempts = 0; heap->nOverflowedAllocations = 0; heap->usage = 0.0f; heap->firstAllocTime = taskTimeElapsed; #endif MEM_MAP_CHAR('@'); for (cookieLink = heap->allocated.head; cookieLink != NULL; cookieLink = cookieLink->next) { //for all allocated blocks bCookie = listGetStructOfNode(cookieLink); mbhCookieVerify(bCookie); #if MEM_USE_NAMES fprintf(fpAnalysis, "%20s, %10d, %10d, %10s, %10x\n", bCookie->name, bCookie->length, heap->blockSize, "allocated", bCookie); #else fprintf(fpAnalysis, "%20s, %10d, %10d, %10s, %10x\n", "noname", bCookie->length, heap->blockSize, "allocated", bCookie); #endif MEM_MAP_CHAR('*'); //print cookie character for (block = 0; block < bCookie->length / MEM_BlockSize; block++) { //print characters for portion of block that is used MEM_MAP_CHAR('#'); } if (bCookie->length % MEM_BlockSize > 0) { //print the partly allocated character MEM_MAP_CHAR('0' + ((bCookie->length % MEM_BlockSize) * 10) / MEM_BlockSize); block++; } for (; block < heap->blockSize / MEM_BlockSize; block++) { //print the remainder of the block(unused space) MEM_MAP_CHAR('.'); } } for (cookieLink = heap->free.head; cookieLink != NULL; cookieLink = cookieLink->next) { //for all free blocks bCookie = listGetStructOfNode(cookieLink); mbhCookieVerify(bCookie); #if MEM_USE_NAMES fprintf(fpAnalysis, "%20s, %10d, %10d, %10s, %10x\n", bCookie->name, 0, heap->blockSize, "free", bCookie); #else fprintf(fpAnalysis, "%20s, %10d, %10d, %10s, %10x\n", "noname", 0, heap->blockSize, "free", bCookie); #endif MEM_MAP_CHAR('*'); //print cookie character for (block = 0; block < heap->blockSize / MEM_BlockSize; block++) { //print characters for portion of block that is used MEM_MAP_CHAR('.'); } } } } #endif //MEM_SMALL_BLOCK_HEAP //print values of the small block pointers for (index = 0; index < MSB_NumberSizes + 1; index++) { fprintf(fpAnalysis, "lowestFree 0x%x = 0x%x\n", ((index) * MEM_BlockSize) + MEM_BlockSize, pool->firstSize[index]); } thisCookie = pool->first; //start walk from very start of heap fprintf(fpAnalysis, "Cookie Name\t\tCookie length\tPrevious block length\tCookie status\n"); //walk through regular heap and print analysis while (thisCookie < pool->last) //scan for free cookies in heap { memCookieVerify(thisCookie); //verify this cookie #if MEM_USE_NAMES fprintf(fpAnalysis, "%20s, %10d, %10d, %10s, %10x\n", thisCookie->name, memBlocksToBytes(thisCookie->blocksNext), memBlocksToBytes(thisCookie->blocksPrevious), bitTest(thisCookie->flags, MBF_AllocatedNext) ? "allocated" : "free", thisCookie); #else fprintf(fpAnalysis, "%20s, %10d, %10d, %10s, %10x\n", "noname", memBlocksToBytes(thisCookie->blocksNext), memBlocksToBytes(thisCookie->blocksPrevious), bitTest(thisCookie->flags, MBF_AllocatedNext) ? "allocated" : "free", thisCookie); #endif MEM_MAP_CHAR('*'); if (bitTest(thisCookie->flags, MBF_AllocatedNext)) { for (block = thisCookie->blocksNext; block > 0; block--) { MEM_MAP_CHAR('#'); } } else { for (block = thisCookie->blocksNext; block > 0; block--) { MEM_MAP_CHAR('.'); } } nextCookie = (memcookie *)((ubyte *)thisCookie + //next cookie sizeof(memcookie) + memBlocksToBytes(thisCookie->blocksNext)); if (nextCookie >= pool->last) //if cookie pointed past end of heap { break; //no more free structures } thisCookie = nextCookie; //next update } #if MEM_LOG_MOSTVOLATILE //print sorted list of memory volatility fprintf(fpAnalysis, "Cookie volatility sorted by average volatility.\n"); fprintf(fpAnalysis, "Name, number allocations logged, average volatility (seconds):\n"); //first pass: compute volatility level for (index = 0; index < MEM_NumberVolatileStats; index++) { //for all cookies if (memVolatileStat[index].nLogged == 0.0f) { //if end of stat list break; } //compute this stat's volatility memVolatileStat[index].volatility = memVolatileStat[index].totalTime / memVolatileStat[index].nLogged; } //sort by volatility qsort(memVolatileStat, index, sizeof(volatilestat), memVolatilitySort); //second pass: print values and clear structures for another logging session for (block = 0; block < index; block++) { fprintf(fpAnalysis, "%16s, %5.0f, %5.2f\n", memVolatileStat[block].name, memVolatileStat[block].nLogged, memVolatileStat[block].volatility); memVolatileStat[block].name[0] = 0; //clear out the name memVolatileStat[block].nLogged = 0.0f; //and the rest of structure memVolatileStat[block].totalTime = 0.0f; } #endif //MEM_LOG_MOSTVOLATILE } /*----------------------------------------------------------------------------- Name : memAnalysisCreate Description : Creates and prints a detailed analysis of the current state of the memory heap. Inputs : void Outputs : Calls the previous 3 memory computation functions, then saves a detailed memory map to disk. Return : ----------------------------------------------------------------------------*/ void memAnalysisCreate(void) { FILE *fpAnalysis, *fpMap; sdword index; dbgMessagef("\nSaving detailed analysis to '%s' and map to '%s'", MEM_ANALYSIS_FILE_NAME, MEM_MAP_FILE_NAME); fpAnalysis = fopen(MEM_ANALYSIS_FILE_NAME, "wt"); fpMap = fopen(MEM_MAP_FILE_NAME, "wt"); if (fpMap == NULL || fpAnalysis == NULL) { dbgWarningf(DBG_Loc, "Error opening either '%s' or '%s'.", MEM_ANALYSIS_FILE_NAME, MEM_MAP_FILE_NAME); return; } memAnalysisCreateForPool(&memMainPool, fpAnalysis, fpMap); for (index = 0; index < memNumberGrowthPools; index++) { memAnalysisCreateForPool(&memGrowthPool[index], fpAnalysis, fpMap); } fclose(fpAnalysis); fclose(fpMap); } #endif //MEM_ANALYSIS /*----------------------------------------------------------------------------- Name : memStrncpy Description : Line strncpy, but it copies the NULL terminator will always be appended. If length of source > count, dest[count - 1] will be a NULL terminator. Inputs : dest - where to copy to source - where to copy from count - max length of dest Outputs : Return : dest ----------------------------------------------------------------------------*/ char *memStrncpy(char *dest, char *source, sdword count) { char *destStart = dest; while (1) { if (count == 1/* && *(source + 1) != 0*/) { *dest = 0; break; } *dest = *source; if (*source == 0/* || count == 0*/) { break; } count--; dest++; source++; } return(destStart); }