Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MemServer : a dynamic memory allocation server

0.00/5 (No votes)
22 Dec 2015 2  
A way to ensure the deallocation of all used dynamic memory using one DLL

Introduction: About dynamic memory allocation

One of the most common operations while programming is the allocation of dynamic memory to store data, which is done by using routines like standard "C" malloc() and calloc() or C++ new operator. There is more than one good reason to use dynamic memory instead of static buffers in our programs, as an example, it allows to get buffer of a specific size which is known only at run-time. This way we can avoid to declare oversized buffers. Probably the most interesting aspect of dynamic allocated buffers is that they can be released once no more useful.

By the other side, this last issue if one of the most problematic aspects of using dynamic memory: Programmers must take care to release all allocated bytes using the available functions, ("C" free() or C++ delete), before the program execution ends or it will remain allocated. This situation is called "Memory Leak" and it should be avoided from the application code even if the operating system can (most of times) recover from it.

A well "planned" code will for sure reduce the possibility of generate memory leaks. Moreover, some programming languages gives to the user constructs/mechanisms which can simplify the work to do (like destructors and garbage collection in C++). But there is still one issue to be considered: What if the program crashes in the middle of the execution before the memory is released?

All things could become more easy if someone will take care to release for us all dynamic memory we have leaved allocated when the execution of our program ends, regardless that it is crashed or has reached and executed the last instruction. A similar mechanism can ensure a "clean" exit form our application, but how it can be implemented?

The idea behind

To realize the mechanism we are talking about we need to know when the application ends, independently from the cause of the termination. We also need that it is still working when the program execution is finished so it must necessary be external to the main application. For these reasons it can be implemented using a DLL: As you probably know, a DLL can contain one function named DllMain() which is an optional entry point called when the DLL is loaded or released from memory:

//  DllMain()  function  prototype.
BOOL  WINAPI  DllMain  (  HINSTANCE  hinstDLL,  
  DWORD  fdwReason,  LPVOID  lpvReserved  );

DllMain() is called when processes using the DLL are beginning or terminating their execution. The two cases (process begin and process termination) are identified by one id which is passed as parameter to the function (the fdwReason parameter), we can know the exact reason why DllMain() routine has been called just checking the value of this parameter. Now we have both the two prerequisites discussed above, anyway, before go deeper in implementation details I think that is better to explain how MemServer can be used.

Introducing MemServer

MemServer is a dynamic memory allocation server which uses the mechanism described above to prevent memory leaks and ensure that all allocated dynamic memory will be released at the program termination. MemServe DLL gives to the user some functions which are a sort of "redefinition" of the standard "C" library malloc(), calloc() and free() routines. Here are the prototypes of the exported functions:

/*
  *  Allocate  ulSize  bytes  of  memory  and  
  *  return  the  adress  of  the  buffer.  
  */
void  *  MSRV_malloc  (  ULONG  ulSize  );

/*
  *  Allocate  a  vector  of  ulNum  elements  each  
  *  one  of  ulSize  bytes  in  size.
  */
void  *  MSRV_calloc  (  ULONG  ulNum,  ULONG  ulSize  );

/*
  *  Release  one  allocated  buffer.
  */
int  MSRV_free  (  void  *pAddress  );

Those function behaves exactly like the homonymous ones and they internally use malloc() and free() to physically allocate the needed memory. The library also provides two new functions which allow to allocate a memory area which start address is aligned (multiple) of a specified number of bytes:

/*
  *  Allocate  ulSize  bytes  of  memory  and  
  *  return  the  adress  of  the  buffer.  
  *  The  buffer  start  address  will  be  multiple  
  *  of  the  specified  number  of  bytes.
  */
void  *  MSRV_aligned_malloc  (  ULONG  ulSize,  ALIGNMENT  iAlign  );

/*
  *  Allocate  a  vector  of  ulNum  elements  each  
  *  one  of  ulSize  bytes  in  size.
  *  The  buffer  start  address  will  be  multiple  
  *  of  the  specified  number  of  bytes.
  */
void  *  MSRV_aligned_calloc  (  ULONG  ulNum,  
  ULONG  ulSize,  ALIGNMENT  iAlign  );

Aligned memory is useful because it can be read/moved faster than unaligned one. Any CPU can access to a certain number of memory bytes using one single read operation. For modern 32 bit Athlon and Pentium processors this number is 4 bytes (exactly 32 bit). If your buffer contains RGB colors data of one BMP file to be copied on the video memory to display the image on the screen, probably the copy operation will be done faster if the buffer is aligned. MemServer supports 2, 4 and 8 bytes alignments (16, 32 and 64 bits respectively). The ALIGNMENT type is an enum which contains the labels you have to use to specify the desired alignment when calling MSRV_aligned_malloc() or MSRV_aligned_calloc() routines. This type is defined inside the MemServer.h header file in the following way:

/*
  *  Supported  memory  alignments.
  */
typedef  enum
{
  MSRV_ALIGN_2  =  2,  //  2  bytes  alignment  (16  bit).
  MSRV_ALIGN_4  =  4,  //  4  bytes  alignment  (32  bit).
  MSRV_ALIGN_8  =  8,  //  8  bytes  alignment  (64  bit).
}  ALIGNMENT;  

Now let's discuss about how to use MemServer in one application.

Include MemServer in your code.

To include MemServer in you application you can simply add the MemServer.lib to your project workspace, and let the compiler do all works, or manually load/unload the library using LoadLibrary() and FreeLibrary() functions. In this last case, you have also to get a pointer to all MemServer routines using GetProcAddress(). Finally, remember include MemServer.h header file in the source file.

Using MemServer.

I think that an example is better than a lot of words, so I will start with an example of MemServer session:

#include  "MemServer.h"

int  iRetVal;

//  Try  to  initialize  the  Server,  set  a  maximum  of  300  buffers.
iRetVal  =  MSRV_init(  300  );
if  (  iRetVal  )
{
  //  Failed  to  initialize  MemServer,
  //  close  the  application  or  try  again.  
  ...
}

...
//  Here  you  can  allocate/deallocate  memory  using  one  of  the
//  provided  functions.
...

//  Close  the  session  and  release  all  allocated  memory.
MSRV_close();

As you can see, to initialize the server you have to call the MSRV_init() routine. This function takes as parameter an integer value which represents the maximum number of allocable buffers. Obviously this value cannot be 0. To terminate the session once finished the work the application must call MSRV_close() which will close the server and release any remaining allocated buffer. Any attempt to allocate memory after MSRV_close() has been called will fail and the used function will return NULL to you. You can anyway call again MSRV_init() to restart to use MemServer.

Now let's talk about how to allocate and deallocate memory. MSRV_malloc(), MSRV_calloc() routines and their "aligned" variants behaves exactly like standard "C" language malloc() and calloc() routines:

#define  BYTE_BUFFER_SIZE  150
#define  STRUCT_BUFFER_SIZE  200

//  One  structure  type.
typedef  struct
{
  ULONG  mLongField;
  BYTE  mByteField;
  
}  ONE_STRUCT,  *lpONE_STRUCT;

//  Pointers  to  store  buffer  addresses.
BYTE  *lpBuffer;
lpONE_STRUCT  lpStructBuffer;

...

//  Allocate  a  buffer  of  150  bytes.
lpBuffer  =  (BYTE  *)  MSRV_malloc(  BYTE_BUFFER_SIZE  );

//  Allocate  a  vector  of  200  structures  initialized  to  0.
lpStructBuffer  =  (lpONE_STRUCT)MSRV_calloc(  
    STRUCT_BUFFER_SIZE,  sizeof(  ONE_STRUCT  )  );

if  (  (  lpBuffer  ==  NULL  )  ||  (  lpStructBuffer  ==  NULL  )  )
{
  //  Failed  to  allocate  memory...
}

once finished to work with the two vectors above you can deallocate then this way:

...

//  Deallocate  bytes  vector.
MSRV_free(  (void  *)lpBuffer  );

//  Deallocated  structure  vector.
MSRV_free(  (void  *)lpStructBuffer  );

MSRV_aligned_malloc() and MSRV_aligned_calloc() routines can be used in the same way as their non-aligned counterparts. The only obvious exception is that they take one more parameter. We can modify the core part of example above to use those two functions instead of MSRV_malloc() and MSRV_calloc():

...
//  Allocate  a  buffer  of  150  bytes  aligned  to  4  bytes  (32  bit).
lpBuffer  =  (BYTE  *)  MSRV_aligned_malloc(  
  BYTE_BUFFER_SIZE,  MSRV_ALIGN_4  );

//  Allocate  a  vector  of  200  structures  
//  initialized  to  0  aligned  to  4  bytes.
lpStructBuffer  =  (lpONE_STRUCT)MSRV_aligned_calloc(  
    STRUCT_BUFFER_SIZE,  sizeof(  ONE_STRUCT  ),  MSRV_ALIGN_4  );

if  (  (  lpBuffer  ==  NULL  )  ||  (  lpStructBuffer  ==  NULL  )  )
{
  //  Failed  to  allocate  memory...
}

How MemServer works

MemServer behavior is based on a allocated memory descriptor pool. This pool is an array of structures which are containing information on all allocated buffers. The pool vector (which is called g_MSRV_Buffer_Pool) has a fixed size but this size is set at run-time when the user call MSRV_init(). The server will refuse to allocate more than this number of buffers. As you can argue, the pool is allocated dynamically and the relative memory will be released when the user calls MSRV_close() or the DLL is downloaded from the process memory space. Pool descriptors are defined in the following way:

struct  __MSRV_Mem_Descriptor  
{
  //  Allocated  memory  area  start  address.
  ULONG  m_pAddress;
  
  //  Memory  area  size  (number  of  bytes).
  ULONG  m_ulSize;
};

The number of used descriptors is kept inside the g_ulMSRV_Used_Ptr global variable. Any time the user call one of the provided memory allocation routines, one free descriptor is filled with the star address and the size of the reserved memory area. As told before, the physical memory allocation is done using malloc() routine. When the user calls MSRV_free() to release memory, the system search the passed pointer descriptor inside the pool and if find it release the relative memory area using standard free() routine.

Memory leaks prevention is implemented easily by calling MSRV_close() when DllMain() is called with fdwReason paramete value set to DLL_PROCESS_DETACH. This value means that the application is closing and the Server DLL is about to be unloaded from the memory. MSRV_close() function will release all used memory using the descriptor pool. It will call MSRV_free() for any used descriptor in g_MSRV_Buffer_Pool vector.

I will end this paragraph adding some notes on aligned memory. As I told before "aligned" means "multiple" of a certain number of bytes. Supported alignment are 2, 4 and 8 bytes, as you can see those numbers are all powers of 2. By the properties of binary numbers, an address multiple of one power of 2 must have a certain number of its' final digits set to 0:

//  Binary  value:
(MSB)x...xxxx0(LSB)b  ->  2  bytes  alignment.
(MSB)x...xxx00(LSB)b  ->  4  bytes  alignment.
(MSB)x...xx000(LSB)b  ->  8  bytes  alignment.

x  =  any  binary  value  (1  or  0).

By this issue, to ge one aligned address, we must mask the final binary digits of the address returned by malloc() routine. The formula is the following:

AlignedAddress  =  (  Address  +  Alignment  )  &
    ~(  0xFFFFFFFFUL  -  (  Alignment  -  1  )  )  
  
Address      =  Address  returned  by  malloc().
Alignment  =  desired  alignment  (2,  4  or  8).

The ( Address + Alignment ) is necessary to avoid that the aligned address is out of the allocated space. This issue means that we have to allocate a maximum of 8 bytes more than the required size.

The test program

The enclosed test program is just a simple Win32 console application which demonstrate how to use MemServer routines and that it can free all allocated memory. You have to use the "Win32 Debug" variant of the library with the example why this variant has a debug system which print some debug messages on the Visual Studio integrated debugger Output Window uing the OutputDebugString() Windows routine. I suggest to execute it in Step by Step mode. To make the demo works, just copy MemServer.dll and MemServe.lib into the Debug directory inside the main project folder, then ensure to add the MemServer project folder path to the include file search path for the demo application. The behavior of the demo is really easy, it allocate a vector of 20 char strings (half aligned and alf unaligned), then fill all strings with one message and finally it deallocates only half of the strings. You also have the possibility to "crash" the application generating a "Division by zero" exception error: simply input a 0 value when prompted. During the program execution you will see some debug messages appearing on the Output Window informing you on the server activity. Once the program ends you will see a list of messages like the following:

...
MemServer:  Freeing  all  allocated  memory..
    [10]  buffers  still  allocated.
      Address  [324008h],  44  bytes.
      Address  [324060h],  40  bytes.
      Address  [3240B8h],  44  bytes.
      Address  [324110h],  40  bytes.
      Address  [324168h],  44  bytes.
      Address  [3241C0h],  40  bytes.
      Address  [324218h],  44  bytes.
      Address  [324270h],  40  bytes.
      Address  [3242C8h],  44  bytes.
      Address  [324320h],  40  bytes.
...

The above message is printed by MSRC_close() when releasing the memory of all still allocated descriptors.

Future Improvements

I've tested MemServer on several versions of Windows OS: 98, Me, 2K and XP, in all cases it seems to be working fine. Anyway there is still some works to do. First of all, the descriptor pool management is not really efficient: the descriptor vector is not sorted and this makes the search for one descriptor very slow. It will be better if the vector is kept sorted using fast algorithms like quick sort. This will allow to use to binary search algorithm to find descriptors into the pool vector. Another not optimized issue is that any time one pointer is deallocated, all subsequent descriptors are shifted back of one position to remove "holes". This operation will take a lot of time if you have a lot of memory allocated. I'm thinking about to mark a descriptor as deallocated (using a new field to the __MSRV_Mem_Descriptor structure) instead of shift the entire vector and perform the "defrag" only when the number of "deleted" descriptor is over a certain threshold.

Final words and acknowledgements

I hope that MemServer can be useful to all of you. You are free to use it in your applications but do not bother me if it will screw up your system: It is provided "as is" without any warranty! I'm would like to receive your comments/suggestions/bug reports, do not hesitate to contact me.

Finally I have to say thank you very much to my girl-friend Rossella who has patiently listened to me any time I was talking to her about MemServer project: She has never shown to be bored even if she does not understand anyting about computer science and Windows programming. She has also encouraged me to wrote this article with her enthusiasm.

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here