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"
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:
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.
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
free() routines. Here are the prototypes of the exported functions:
void * MSRV_malloc ( ULONG ulSize );
void * MSRV_calloc ( ULONG ulNum, ULONG ulSize );
int MSRV_free ( void *pAddress );
Those function behaves exactly like the homonymous ones and they internally use
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:
void * MSRV_aligned_malloc ( ULONG ulSize, ALIGNMENT iAlign );
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_calloc() routines. This type is defined inside the
MemServer.h header file in the following way:
MSRV_ALIGN_2 = 2, MSRV_ALIGN_4 = 4, MSRV_ALIGN_8 = 8, } 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
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.
I think that an example is better than a lot of words, so I will start with an example of MemServer session:
iRetVal = MSRV_init( 300 );
if ( iRetVal )
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_calloc() routines and their "aligned" variants behaves exactly like standard "C" language
#define BYTE_BUFFER_SIZE 150
#define STRUCT_BUFFER_SIZE 200
} ONE_STRUCT, *lpONE_STRUCT;
lpBuffer = (BYTE *) MSRV_malloc( BYTE_BUFFER_SIZE );
lpStructBuffer = (lpONE_STRUCT)MSRV_calloc(
STRUCT_BUFFER_SIZE, sizeof( ONE_STRUCT ) );
if ( ( lpBuffer == NULL ) || ( lpStructBuffer == NULL ) )
once finished to work with the two vectors above you can deallocate then this way:
MSRV_free( (void *)lpBuffer );
MSRV_free( (void *)lpStructBuffer );
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
lpBuffer = (BYTE *) MSRV_aligned_malloc(
BYTE_BUFFER_SIZE, MSRV_ALIGN_4 );
lpStructBuffer = (lpONE_STRUCT)MSRV_aligned_calloc(
STRUCT_BUFFER_SIZE, sizeof( ONE_STRUCT ), MSRV_ALIGN_4 );
if ( ( lpBuffer == NULL ) || ( lpStructBuffer == NULL ) )
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:
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
Memory leaks prevention is implemented easily by calling
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
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:
(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).
( 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
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..
 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.
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 at the following e-mail address: firstname.lastname@example.org
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.