Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

Your assignment is to create a Memory Manager which will allow the user to alloc

ID: 3765394 • Letter: Y

Question

Your assignment is to create a Memory Manager which will allow the user to allocate and release memory. Your program also needs to keep track of how much memory is available, how much has been used, where the next available memory area is, etc.

Solution Requirements

---------------------

Your solution should compile and be capable of managing a variable number of allocations and deallocations, with no requirements for deallocations to be in the same order as the allocations. However, a pointer will never be deallocated more than once.

You should provide implementations of the five functions within MemoryManager.cpp.

!! DO NOT MODIFY MemoryManager.h !!

You can define additional functions within MemoryManager.cpp as necessary, but do not change the MemoryManager interface.

Please do not include any other header files. If you need to do this for development, please remove it before submission. For example if you include for printf, please remove the printf (or comment them out) as well as the #include.

Memory Restrictions

-------------------

- No memory dynamically allocated during program execution (new, malloc, etc.).

- All data must fit inside MM_pool[ ]

- No other static or global variables

Common Cases

------------

On average while your system is running, there will be allocations that range in size from 2 bytes to 16k. You may expect 10% of the allocations will be 8 bytes or smaller. Note that despite the average case you may be asked to satisfy any sized allocation

//header file

#pragma once

#ifndef __MEMORY_MANAGER_H__

#define __MEMORY_MANAGER_H__

// DO NOT CHANGE THIS HEADER FILE

namespace MemoryManager

{

//--- CORE Functions, these will need to be completed by the applicant

// Initialize any data needed to manage the memory pool

void initializeMemoryManager(void);

// return a pointer inside the memory pool

// If no chunk can accommodate aSize call OnAllocFail()

void* allocate(int aSize);

// Free up a chunk previously allocated

void deallocate(void* aPointer);

//--- support routines

// Will scan the memory pool and return the total free space remaining

int freeRemaining(void);

// Will scan the memory pool and return the largest free space remaining

int largestFree(void);

// will scan the memory pool and return the smallest free space remaining

int smallestFree(void);
//--- error conditions. None of these functions will return
//--- These routines do not need to be implemented by the candidate
// Call if no space is left for the allocation request

void onOutOfMemory(void);

// Call if a pointer over run condition is detected

void onOverrunDetected(void);

// If the caller makes any illegal request your code should call this

// provided failure function (which will not return):

void onIllegalOperation(const char* fmt,...);

// eg:

// int errorCode;

// onIllegalOperation("Error in createQueue: %d", errorCode);

};

#endif // __MEMORY_MANAGER_H

//cpp file

#include "MemoryManager.h"

//MemoryManager.cpp

namespace MemoryManager

{
// This is the only static memory that you may use, no other global variables

// may be created, if you need to save data make it fit in MM_pool

// IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT

const int MM_POOL_SIZE = 65536;

char MM_pool[MM_POOL_SIZE];

// Initialize set up any data needed to manage the memory pool

void initializeMemoryManager(void)

{

// TODO : IMPLEMENT ME

}

// return a pointer inside the memory pool

// If no chunk can accommodate aSize call onOutOfMemory()

void* allocate(int aSize)

{

// TODO: IMPLEMENT ME

return ((void*) 0);

}

// Free up a chunk previously allocated

void deallocate(void* aPointer)

{

// TODO: IMPLEMENT ME

}

//---

//--- support routines

//---

// Will scan the memory pool and return the total free space remaining

int freeRemaining(void)

{

// TODO: IMPLEMENT ME

return 0;

}

// Will scan the memory pool and return the largest free space remaining

int largestFree(void)

{

// TODO: IMPLEMENT ME

return 0;

}

// will scan the memory pool and return the smallest free space remaining

int smallestFree(void)

{

// TODO: IMPLEMENT ME

return 0;

}
}

// main

#include "MemoryManager.h"
#include <iostream>
using namespace std;

int main(void)

{

using namespace MemoryManager;

initializeMemoryManager();


long* int_pointer;

char* string_pointer;

std::cout << "Free memory = " << freeRemaining() << std::endl;

int_pointer = (long *) allocate(sizeof(long));

std::cout<<"int_pointer Mem Address:"<<(long )int_pointer<

std::cout << "Free memory = " << freeRemaining() << std::endl;

std::cout << "Largest Free Memory Block = " << largestFree() << std::endl;

string_pointer = (char*) allocate(255);

std::cout<<"string_pointer Mem Address:"<<(long )string_pointer<

//std::cout<<"checkpoint 1 ";

//std::cin>>a;

*int_pointer = 0xDEADBEEF;

*string_pointer = 'X';

*(string_pointer+1) = 'Y';

*(string_pointer+2) = 'Z';

*(string_pointer+3) = '';

std::cout<<< std::endl;

std::cout << "Free memory = " << freeRemaining() << std::endl;

std::cout << "Largest Free Memory Block = " << largestFree() << std::endl;

//std::cout<<"checkpoint 2 ";

//std::cin>>a;

strcpy(string_pointer,"It was the best of times, it was the worst of times");

std::cout<<

//std::cout<<"checkpoint 3 ";

std::cout << "Free memory = " << freeRemaining() << std::endl;

std::cout << "Largest Free Memory Block = " << largestFree() << std::endl;

deallocate(int_pointer);

deallocate(string_pointer);


int a;

std::cin>>a;

}

namespace MemoryManager

{

void onOutOfMemory(void)

{

std::cerr << "Memory pool out of memory" << std::endl;

exit( 1 );

}

// Call if a pointer over run condition is detected

void onOverrunDetected(void)

{

std::cerr << "Pointer overrun detected" << std::endl;

exit( 1 );

}

// call for any other error condition, providing meaningful error messages are appreciated

void onIllegalOperation(const char* fmt,...)

{

if ( fmt == NULL )

{

std::cerr << "Unknown illegal operation" << std::endl;

exit( 1 );

}

else

{

char   buf[8192];

va_list argptr;

va_start (argptr,fmt);

vsprintf (buf,fmt,argptr);

va_end (argptr);

std::cerr << "Illegal operation: "" << buf << """ << std::endl;

exit( 1 );

}

}

}

Output should be:

Free memory = 65532

int_pointer Mem Address: 9192500

Free memory = 65522

Largest Free Memory Block = 65522

string_pointer Mem Address:9192510

XYZ

Free memory = 65261

It was the best of times, it was the worst of time

Free memory = 65261

Largest Free Memory Block = 65261

Plz implement only file MemoryManager.cpp, tks!!!

Explanation / Answer

  Dynamic Memory in C

In C, dynamic memory is allocated from the heap using some standard library functions. The two key dynamic memory functions are malloc() and free().

The malloc() function takes a single parameter, which is the size of the requested memory area in bytes. It returns a pointer to the allocated memory. If the allocation fails, it returns NULL. The prototype for the standard library function is like this:

          void *malloc(size_t size);

The free() function takes the pointer returned by malloc() and de-allocates the memory. No indication of success or failure is returned. The function prototype is like this:

          void free(void *pointer);

To illustrate the use of these functions, here is some code to statically define an array and set the fourth element’s value:

         int my_array[10];
         my_array[3] = 99;

The following code does the same job using dynamic memory allocation:

         int *pointer;
         pointer = malloc(10 * sizeof(int));
         *(pointer+3) = 99;

The pointer de-referencing syntax is hard to read, so normal array referencing syntax may be used, as [ and ] are just operators:

          pointer[3] = 99;

When the array is no longer needed, the memory may be de-allocated thus:

       free(pointer);
       pointer = NULL;

Assigning NULL to the pointer is not compulsory, but is good practice, as it will cause an error to be generated if the pointer is erroneous utilized after the memory has been de-allocated.

The amount of heap space actually allocated by malloc() is normally one word larger than that requested. The additional word is used to hold the size of the allocation and is for later use by free(). This “size word” precedes the data area to which malloc() returns a pointer.

There are two other variants of the malloc() function: calloc() and realloc().

The calloc() function does basically the same job as malloc(), except that it takes two parameters – the number of array elements and the size of each element – instead of a single parameter (which is the product of these two values). The allocated memory is also initialized to zeros. Here is the prototype:

          void *calloc(size_t nelements, size_t elementSize);

The realloc() function resizes a memory allocation previously made by malloc(). It takes as parameters a pointer to the memory area and the new size that is required. If the size is reduced, data may be lost. If the size is increased and the function is unable to extend the existing allocation, it will automatically allocate a new memory area and copy data across. In any case, it returns a pointer to the allocated memory. Here is the prototype:

void *realloc(void *pointer, size_t size);

Dynamic Memory in C++

Management of dynamic memory in C++ is quite similar to C in most respects. Although the library functions are likely to be available, C++ has two additional operators – new and delete – which enable code to be written more clearly, succinctly and flexibly, with less likelihood of errors. The new operator can be used in three ways:

        p_var = new typename;
        p_var = new type(initializer);
        p_array = new type [size];

In the first two cases, space for a single object is allocated; the second one includes initialization. The third case is the mechanism for allocating space for an array of objects.

The delete operator can be invoked in two ways:

          delete p_var;
          delete[] p_array;

The first is for a single object; the second deallocates the space used by an array. It is very important to use the correct de-allocator in each case.

There is no operator that provides the functionality of the C realloc() function.

Here is the code to dynamically allocate an array and initialize the fourth element:

      int* pointer;
      pointer = new int[10];
      pointer[3] = 99;

Using the array access notation is natural. De-allocation is performed thus:

      delete[] pointer;
      pointer = NULL;

Again, assigning NULL to the pointer after deallocation is just good programming practice. Another option for managing dynamic memory in C++ is the use the Standard Template Library. This may be inadvisable for real time embedded systems.

Issues and Problems

As a general rule, dynamic behavior is troublesome in real time embedded systems. The two key areas of concern are determination of the action to be taken on resource exhaustion and nondeterministic execution performance.

There are a number of problems with dynamic memory allocation in a real time system. The standard library functions (malloc() and free()) are not normally reentrant, which would be problematic in a multithreaded application. If the source code is available, this should be straightforward to rectify by locking resources using RTOS facilities (like a semaphore). A more intractable problem is associated with the performance of malloc(). Its behavior is unpredictable, as the time it takes to allocate memory is extremely variable. Such nondeterministic behavior is intolerable in real time systems.

Without great care, it is easy to introduce memory leaks into application code implemented using malloc() and free(). This is caused by memory being allocated and never being deallocated. Such errors tend to cause a gradual performance degradation and eventual failure. This type of bug can be very hard to locate.

Memory allocation failure is a concern. Unlike a desktop application, most embedded systems do not have the opportunity to pop up a dialog and discuss options with the user. Often, resetting is the only option, which is unattractive. If allocation failures are encountered during testing, care must be taken with diagnosing their cause. It may be that there is simply insufficient memory available – this suggests various courses of action. However, it may be that there is sufficient memory, but not available in one contiguous chunk that can satisfy the allocation request. This situation is called memory fragmentation.

Memory Fragmentation

The best way to understand memory fragmentation is to look at an example. For this example, it is assumed hat there is a 10K heap. First, an area of 3K is requested, thus:

         #define K (1024)
         char *p1;
         p1 = malloc(3*K);

Then, a further 4K is requested:

        p2 = malloc(4*K);

3K of memory is now free.

Some time later, the first memory allocation, pointed to by p1, is de-allocated:

        free(p1);

This leaves 6K of memory free in two 3K chunks. A further request for a 4K allocation is issued:

       p1 = malloc(4*K);

This results in a failure – NULL is returned into p1 – because, even though 6K of memory is available, there is not a 4K contiguous block available. This is memory fragmentation.

It would seem that an obvious solution would be to de-fragment the memory, merging the two 3K blocks to make a single one of 6K. However, this is not possible because it would entail moving the 4K block to which p2 points. Moving it would change its address, so any code that has taken a copy of the pointer would then be broken. In other languages (such as Visual Basic, Java and C#), there are defragmentation (or “garbage collection”) facilities. This is only possible because these languages do not support direct pointers, so moving the data has no adverse effect upon application code. This defragmentation may occur when a memory allocation fails or there may be a periodic garbage collection process that is run. In either case, this would severely compromise real time performance and determinism.

Memory with an RTOS

A real time operating system may provide a service which is effectively a reentrant form of malloc(). However, it is unlikely that this facility would be deterministic.

Memory management facilities that are compatible with real time requirements – i.e. they are deterministic – are usually provided. This is most commonly a scheme which allocates blocks – or “partitions” – of memory under the control of the OS.

Block/partition Memory Allocation

Typically, block memory allocation is performed using a “partition pool”, which is defined statically or dynamically and configured to contain a specified number of blocks of a specified fixed size. For Nucleus OS, the API call to define a partition pool has the following prototype:

STATUS
   NU_Create_Partition_Pool (NU_PAR TITION_POOL *pool, CHAR *name, VOID *start_address, UNSIGNED pool_size, UNSIGNED partition_size, OPTION suspend_type);

This is most clearly understood by means of an example:

   status = NU_Create_Partition_Pool(&MyPoo l, "any name", (VOID *) 0xB000, 2000, 40, NU_FIFO);

This creates a partition pool with the descriptor MyPool, containing 2000 bytes of memory, filled with partitions of size 40 bytes (i.e. there are 50 partitions). The pool is located at address 0xB000. The pool is configured such that, if a task attempts to allocate a block, when there are none available, and it requests to be suspended on the allocation API call, suspended tasks will be woken up in a first-in, first-out order. The other option would have been task priority order.

Another API call is available to request allocation of a partition. Here is an example using Nucleus OS:

      status = NU_Allocate_Partition(&MyPool, &ptr, NU_SUSPEND);

This requests the allocation of a partition from MyPool. When successful, a pointer to the allocated block is returned in ptr. If no memory is available, the task is suspended, because NU_SUSPEND was specified; other options, which may have been selected, would have been to suspend with a timeout or to simply return with an error.

When the partition is no longer required, it may be de-allocated thus:

      status = NU_Deallocate_Partition(ptr);

If a task of higher priority was suspended pending availability of a partition, it would now be run. There is no possibility for fragmentation, as only fixed size blocks are available. The only failure mode is true resource exhaustion, which may be controlled and contained using task suspend, as shown.

Additional API calls are available which can provide the application code with information about the status of the partition pool – for example, how many free partitions are currently available. Care is required in allocating and de-allocating partitions, as the possibility for the introduction of memory leaks remains.

Memory Leak Detection

The potential for programmer error resulting in a memory leak when using partition pools is recognized by vendors of real time operating systems. Typically, a profiler tool is available which assists with the location and rectification of such bugs.

Real Time Memory Solutions

Having identified a number of problems with dynamic memory behavior in real time systems, some possible solutions and better approaches can be proposed.

Dynamic Memory

It is possible to use partition memory allocation to implement malloc() in a robust and deterministic fashion. The idea is to define a series of partition pools with block sizes in a geometric progression; e.g. 32, 64, 128, 256 bytes. A malloc() function may be written to deterministically select the correct pool to provide enough space for a given allocation request. This approach takes advantage of the deterministic behavior of the partition allocation API call, the robust error handling (e.g. task suspend) and the immunity from fragmentation offered by block memory.

The standard C library routines for allocating and freeing memory are malloc() and free().

Standard C library memory function prototypes

void *malloc ( size_t size );

void free   ( void *memblock );

C program, with memory management problems

#include <stdlib.h>

int main(void)

{

    void *pMem=malloc(100);

    free(pMem);

    free(pMem);

    return 0;

} /* main */

All the other heap manager requirements can be met within the heap manager. With this information, the prototypes to the heap manager interface can be written as follows.

The heap manager interface

EXTERNC LPVOID APIENTRY FmNew      ( SIZET, LPCLASSDESC, LPSTR, int);

EXTERNC LPVOID APIENTRY FmFree     ( LPVOID );

EXTERNC LPVOID APIENTRY FmRealloc ( LPVOID, SIZET, LPSTR, int );

EXTERNC LPVOID APIENTRY FmStrDup   ( LPSTR, LPSTR, int );

EXTERNC void   APIENTRY FmWalkHeap ( void );

EXTERNC BOOL   APIENTRY FmIsPtrOk ( LPVOID );

he prefix and postfix structures

/*--- Declare what LPPREFIX/LPPOSTFIX are ---*/

typedef struct tagPREFIX FAR*LPPREFIX;

typedef struct tagPOSTFIX FAR*LPPOSTFIX;

/*--- Prefix structure before every heap object---*/

typedef struct tagPREFIX {

    LPPREFIX lpPrev;           /* previous object in heap      */

    LPPREFIX lpNext;           /* next object in heap          */

    LPPOSTFIX lpPostfix;       /* ptr to postfix object        */

    LPSTR lpFilename;          /* filename ptr or NULL         */

    long lLineNumber;          /* line number or 0             */

    LPVOID lpMem;              /* FmNew() ptr of object        */

    LPCLASSDESC lpClassDesc;   /* class descriptor ptr or NULL */

    } PREFIX;

/*--- Postfix structure after every heap object ---*/

typedef struct tagPOSTFIX {

    LPPREFIX lpPrefix;

    } POSTFIX;

uaranteeing correct prefix structure alignment

#define ALIGNMENT (sizeof(int))

CompilerAssert(ISPOWER2(ALIGNMENT));

CompilerAssert(!(sizeof(PREFIX)%ALIGNMENT));

ligning a memory size, assumes no overflow

#define DOALIGN(num) (((num)+ALIGNMENT-1)&~(ALIGNMENT-1))

mNew() function

LPVOID APIENTRY FmNew( SIZET wSize, LPCLASSDESC lpClassDesc,

    LPSTR lpFile, int nLine )

{

LPPREFIX lpPrefix;

wSize = DOALIGN(wSize);

lpPrefix=(LPPREFIX)malloc(sizeof(PREFIX)+wSize+sizeof(POSTFIX));

if (lpPrefix) {

    AddToLinkedList( lpPrefix );

    lpPrefix->lpPostfix = (LPPOSTFIX)((LPSTR)(lpPrefix+1)+wSize);

    lpPrefix->lpPostfix->lpPrefix = lpPrefix;

    lpPrefix->lpFilename = lpFile;

    lpPrefix->lLineNumber = nLine;

    lpPrefix->lpMem = lpPrefix+1;

    lpPrefix->lpClassDesc = lpClassDesc;

    memset( lpPrefix->lpMem, 0, wSize );

    }

else {

    AssertError;             /* Report out of memory error */

    }

return(lpPrefix ? lpPrefix+1 : NULL);

} /* FmNew */

ddToLinkedList() function

static LPPREFIX lpHeapHead=NULL;

void LOCAL AddToLinkedList( LPPREFIX lpAdd )

{

    /*--- Add before current head of list ---*/

    if (lpHeapHead) {

        lpAdd->lpPrev = lpHeapHead->lpPrev;

        (lpAdd->lpPrev)->lpNext = lpAdd;

        lpAdd->lpNext = lpHeapHead;

        (lpAdd->lpNext)->lpPrev = lpAdd;

        }

    /*--- Else first node ---*/

    else {

        lpAdd->lpPrev = lpAdd;

        lpAdd->lpNext = lpAdd;

        }

    /*--- Make new item head of list ---*/

    lpHeapHead = lpAdd;

} /* AddToLinkedList */


Once added to the linked list, lpPostfix is filled in to point to the postfix structure. The lpPrefix data item within the postfix structure is then filled in. It simply points back to the prefix structure. Next, the lpFilename and lLineNumber data items are initialized from the arguments passed to FmNew(). Finally, the lpMem and lpClassDesc data items are initialized for purposes of run-time type checking.

Finally, the block of user memory is zero initialized by calling memset().

5.2.6 FmFree() Implementation

FmFree() function

LPVOID APIENTRY FmFree( LPVOID lpMem )

{

    if (VerifyHeapPointer(lpMem)) {

       LPPREFIX lpPrefix=(LPPREFIX)lpMem-1;

       SIZET wSize=(LPSTR)(lpPrefix->lpPostfix+1)-(LPSTR)lpPrefix;

       RemoveFromLinkedList( lpPrefix );

       memset( lpPrefix, 0, wSize );

       free(lpPrefix);

       }

    return (NULL);

} /* FmFree */


The FmFree() implementation is straightforward, provided that an effective VerifyHeapPointer() can be written. This function has all the implementation problems that run-time type checking has. VerifyHeapPointer(), in order to be completely robust, must be tailored to a specific machine architecture.

Provided that the memory pointer passed to FmFree() is indeed a valid heap pointer, VerifyHeapPointer() allows the body of FmFree() to execute. The first thing to do is calculate a pointer to the prefix structure. The size of the object is calculated next and assigned to wSize. The heap object pointed to is removed by calling RemoveFromLinkedList().

RemoveFromLinkedList() function

void LOCAL RemoveFromLinkedList( LPPREFIX lpRemove )

{

    /*--- Remove from doubly linked list ---*/

    (lpRemove->lpPrev)->lpNext = lpRemove->lpNext;

    (lpRemove->lpNext)->lpPrev = lpRemove->lpPrev;

    /*--- Possibly correct head pointer ---*/

    if (lpRemove==lpHeapHead) {

        lpHeapHead = ((lpRemove->lpNext==lpRemove) ? NULL :

            lpRemove->lpNext);

        }

} /* RemoveFromLinkedList */


Once the memory object has been removed from the doubly linked list, the memory block is initialized to zero.

Finally, free() is called to deallocate the memory block. The 32-bit memory deallocator may be called by other names in other environments. Simply replace free() with a call that is appropriate to your environment.

NULL is always returned from the FmFree() function. This is done to help implement the policy that a pointer is either valid or it is NULL (see §7.12). A pointer variable should never point to an invalid memory object. The memory macros use the NULL return value to store in the memory pointer passed to FmFree().

5.2.7 VerifyHeapPointer() Implementation

VerifyHeapPointer() function

BOOL LOCAL VerifyHeapPointer( LPVOID lpMem )

{

BOOL bOk=FALSE;

if (lpMem) {

    WinAssert(FmIsPtrOk(lpMem)) {

      LPPREFIX lpPrefix=(LPPREFIX)lpMem-1;

      WinAssert(lpPrefix->lpMem==lpMem) {

        WinAssert(lpPrefix->lpPostfix->lpPrefix==lpPrefix) {

          bOk = TRUE;

          }

        }

      }

    }

return (bOk);

} /* VerifyHeapPointer */


VerifyHeapPointer() code first checks to determine whether the pointer passed to it is NULL or not. Next, the lpMem pointer is validated to make sure that it is a valid pointer into the heap. This is done by calling FmIsPtrOk(). Then, a pointer to the prefix structure is obtained and the memory pointer in the structure is checked against the memory pointer being validated. Finally, the prefix pointer in the postfix structure is validated. Most types of memory overwrites past the end of an object trash the prefix pointer in the postfix structure. This is how memory overwrites are detected.

5.2.8 FmIsPtrOk() Implementation

The VerifyHeapPointer() must be called only by the heap management functions and is a local function to the heap management module. This function is complete except for the FmIsPtrOk() function. FmIsPtrOk() validates that a pointer passed to it is a realistic heap pointer.

You must code a FmIsPtrOk() function that is appropriate for your environment.

The FmIsPtrOk() function is defined as follows.

FmIsPtrOk() function, for Intel segmented protected-mode application

BOOL APIENTRY FmIsPtrOk( LPVOID lpMem )

{

    BOOL bOk=FALSE;

    _asm xor ax, ax                  ;; assume bad selector

    _asm lsl ax, word ptr [lpMem+2] ;; get selector limit

    _asm cmp word ptr [lpMem], ax    ;; is ptr offset under limit

    _asm jae done                    ;; no, bad pointer

    _asm mov bOk, 1                  ;; yes, pointer OK

    _asm done:

    return (bOk);

} /* FmIsPtrOk */


The key to this implementation of the FmIsPtrOk() function is the usage of lsl. This assembly instruction loads a register (in this case, register ax) with the limit of the provided selector (in this case, the selector of lpMem). If the offset of the lpMem pointer is within the limit of the memory segment pointed to by the selector of lpMem, the lpMem memory pointer is considered valid.


For a non-protected-mode application, the FmIsPtrOk() function is as follows.

FmIsPtrOk() function, for non-protected-mode application

BOOL APIENTRY FmIsPtrOk( LPVOID lpMem )

{

    return ((lpMem) && (!((long)lpMem&(ALIGNMENT-1))));

} /* FmIsPtrOk */


In a non-protected-mode environment, FmIsPtrOk() can assume only that the pointer is OK if the pointer is non-NULL and properly aligned.

5.2.9 FmWalkHeap() Implementation

An important part of any heap manager is the ability to display a symbolic heap dump.

FmWalkHeap() function

void APIENTRY FmWalkHeap( void )

{

  if (lpHeapHead) {

        LPPREFIX lpCur=lpHeapHead;

        while (VerifyHeapPointer(&lpCur[1])) {

            char buffer[100];

            RenderDesc( lpCur, buffer );

            /*--- print out buffer ---*/

            /* printf( "walk: %s ", buffer ); */

            lpCur = lpCur->lpNext;

            if (lpCur==lpHeapHead) {

                break;

                }

            }

        }

} /* FmWalkHeap */


The implementation of FmWalkHeap() is pretty straightforward. It walks the heap getting descriptions of objects and prints them out. The implementation of getting a description of an object is left to RenderDesc().

RenderDesc() function

void LOCAL RenderDesc( LPPREFIX lpPrefix, LPSTR lpBuffer )

{

    if (lpPrefix->lpMem==&lpPrefix[1]) {

        sprintf( lpBuffer, "%08lx ", lpPrefix );

        if (lpPrefix->lpFilename) {

            sprintf( lpBuffer+strlen(lpBuffer), "%12s %4ld ",

                lpPrefix->lpFilename, lpPrefix->lLineNumber );

            }

        if (lpPrefix->lpClassDesc) {

            sprintf( lpBuffer+strlen(lpBuffer), "%s",

                lpPrefix->lpClassDesc->lpVarName );

            }

        }

    else {

        strcpy( lpBuffer, "(bad)" );

        }

} /* RenderDesc */


This is one possible implementation of RenderDesc(). It displays the address of an object, its filename and line number, if any, and its class descriptor name, if any.

5.2.10 FmRealloc() Implementation

Another memory management function that needs a new interface is realloc(). While not presented here, the source for FmRealloc() can be found in the Code Listings Appendix.

5.3 An Interface for Strings

How should strings be dynamically allocated from the heap? Who is responsible for calling the heap interface? The best way to approach this problem is through the utilization of an abstraction layer. It is best to implement the string interface through a set of macros.

5.3.1 NEWSTRING() Macro

The first macro is NEWSTRING(). It is used to allocate storage for a string from the heap.

NEWSTRING() macro

#define NEWSTRING(lpDst,wSize)

(_LPV(lpDst)=FmNew((SIZET)(wSize),NULL,szSRCFILE,__LINE__))


NEWSTRING() can almost be considered a replacement for malloc(). The macro takes two arguments. The first argument is the name of a variable that is to contain the pointer of the allocated block of memory. The second argument is the size in bytes of the block of memory to allocate. This macro was designed this way so that the _LPV() macro could be used to avoid type casting of the pointer returned from FmNew().

To free the memory allocated through NEWSTRING(), simply call FREE().

5.3.2 MYLSTRDUP() Macro

The second macro is MYLSTRDUP(). It is a replacement for strdup() that uses the new heap manager interface.

MYLSTRDUP() macro

#define MYLSTRDUP(lpDst,lpSrc)

(_LPV(lpDst)=FmStrDup(lpSrc,szSRCFILE,__LINE__))


MYLSTRDUP() is a macro interface around FmStrDup(). It is a new heap manager function that does the dirty work of duplicating the string.

FmStrDup()

LPVOID APIENTRY FmStrDup( LPSTR lpS, LPSTR lpFile, int nLine )

{

    LPVOID lpReturn=NULL;

    if (lpS) {

        SIZET wSize = (SIZET)(strlen(lpS)+1);

        lpReturn = FmNew( wSize, NULL, lpFile, nLine );

        if (lpReturn) {

            memcpy( lpReturn, lpS, wSize );

            }

        }

    return(lpReturn);

} /* FmStrDup */


If a NULL pointer is passed into FmStrDup(), a NULL pointer is returned. FmStrDup() works by first calculating the string length and then adding one for the null character. This number of bytes is then allocated by calling FmNew() and finally the string is copied into the new memory buffer.

5.4 An Interface for Arrays

Just as with strings, using macros as a level ob abstraction greatly simplifies using arrays.

5.4.1 NEWARRAY() Macro

The NEWARRAY() macro is used to create a new array with a specific number of array elements.

NEWARRAY macro

#define NEWARRAY(lpArray, wSize)

(_LPV(lpArray)=FmNew((SIZET)(sizeof(*(lpArray))*(wSize)),

NULL,szSRCFILE,__LINE__))


NEWARRAY() takes as its first argument the name of the variable that is to contain the pointer of the allocated array. The second argument is the number of array elements (not bytes) to allocate.

To free the memory allocated through NEWARRAY(), simply call FREE().

5.4.2 SIZEARRAY() Macro

The SIZEARRAY() macro is used to resize an array.

SIZEARRAY Macro

#define SIZEARRAY(lpArray, wSize)

(_LPV(lpArray)=FmRealloc((lpArray),

(SIZET)(sizeof(*(lpArray))*(wSize)),szSRCFILE,__LINE__))


SIZEARRAY() takes as its first argument the name of the variable that points to an allocated array. If it is NULL, SIZEARRAY() allocates a new array. The second argument is the new size of the array in array elements (not bytes).

Standard C library memory function prototypes

void *malloc ( size_t size );

void free   ( void *memblock );

C program, with memory management problems

#include <stdlib.h>

int main(void)

{

    void *pMem=malloc(100);

    free(pMem);

    free(pMem);

    return 0;

} /* main */

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
Chat Now And Get Quote