Managing memory with multiple heaps

You can use XL C/C++ to create and manipulate your own memory heaps, either in place of or in addition to the default XL C runtime heap.

You can create heaps of regular memory or shared memory, and you can have any number of heaps of any type. The only limit is the space available on your operating system (your machine's memory and swapper size, minus the memory required by other running applications). You can also change the default runtime heap to a heap that you have created.

Using your own heaps is optional, and your applications will work well using the default memory management provided (and used by) the XL C/C++ runtime library. However, using multiple heaps can be more efficient and can help you improve your program's performance and reduce wasted memory for a number of reasons:

The following sections describe the functions available for using multiple heaps, provide programming guidelines for creating, using and destroying multiple heaps, and provide code examples that implement multiple heaps.

Functions for managing user-created heaps

The libhu.a library provides a set of functions that allow you to manage user-created heaps. These functions are all prefixed by _u (for "user" heaps), and they are declared in the header file umalloc.h. The following table summarizes the functions available for creating and managing user-defined heaps.

Table 11. Functions for managing memory heaps
Default heap function Corresponding user-created heap function Description
n/a _ucreate Creates a heap. Described in Creating a heap.
n/a _uopen Opens a heap for use by a process. Described in Using a heap.
n/a _ustats Provides information about a heap. Described in Getting information about a heap.
n/a _uaddmem Adds memory blocks to a heap. Described in Expanding a heap.
n/a _uclose Closes a heap from further use by a process. Described in Closing and destroying a heap.
n/a _udestroy Destroys a heap. Described in Closing and destroying a heap.
calloc _ucalloc Allocates and initializes memory from a heap you have created. Described in Using a heap.
malloc _umalloc Allocates memory from a heap you have created. Described in Using a heap.
_heapmin _uheapmin Returns unused memory to the system. Described in Closing and destroying a heap.
n/a _udefault Changes the default runtime heap to a user-created heap. Described in Changing the default heap used in a program.
Note:
There are no user-created heap versions of realloc or free. These standard functions always determine the heap from which memory is allocated, and can be used with both user-created and default memory heaps.

Creating a heap

You can create a fixed-size heap, or a dynamically sized heap. With a fixed-size heap, the initial block of memory must be large enough to satisfy all allocation requests made to it. With a dynamically-sized heap, the heap can expand and contract as your program needs demand.

Creating a fixed-size heap

When you create a fixed-size heap, you first allocate a block of memory large enough to hold the heap and to hold internal information required to manage the heap, and you assign it a handle. For example:

Heap_t fixedHeap;    /* this is the "heap handle" */
/* get memory for internal info plus 5000 bytes for the heap */
static char block[_HEAP_MIN_SIZE + 5000];  

The internal information requires a minimum set of bytes, specified by the _HEAP_MIN_SIZE macro (defined in umalloc.h). You can add the amount of memory your program requires to this value to determine the size of the block you need to get. Once the block is fully allocated, further allocation requests to the heap will fail.

After you have allocated a block of memory, you create the heap with _ucreate, and specify the type of memory for the heap, regular or shared. For example:

fixedHeap = _ucreate(block, (_HEAP_MIN_SIZE+5000),  /* block to use */
                     !_BLOCK_CLEAN,   /* memory is not set to 0   */
                     _HEAP_REGULAR,   /* regular memory           */
                     NULL, NULL);     /* functions for expanding and shrinking 
                                         a dynamically-sized heap */

The !_BLOCK_CLEAN parameter indicates that the memory in the block has not been initialized to 0. If it were set to 0 (for example, by memset), you would specify _BLOCK_CLEAN. The calloc and _ucalloc functions use this information to improve their efficiency; if the memory is already initialized to 0, they don't need to initialize it.

The fourth parameter indicates the type of memory the heap contains: regular (_HEAP_REGULAR) or shared (_HEAP_SHARED).

For a fixed-size heap, the last two parameters are always NULL.

Creating a dynamically-sized heap

With the XL C/C++default heap, when not enough storage is available to fulfill a malloc request, the runtime environment gets additional storage from the system. Similarly, when you minimize the heap with _heapmin or when your program ends, the runtime environment returns the memory to the operating system.

When you create an expandable heap, you provide your own functions to do this work, which you can name however you choose. You specify pointers to these functions as the last two parameters to _ucreate (instead of the NULL pointers you use to create a fixed-size heap). For example:

Heap_t growHeap;
static char block[_HEAP_MIN_SIZE];  /* get block */

growHeap = _ucreate(block, _HEAP_MIN_SIZE,   /* starting block */
                    !_BLOCK_CLEAN,      /* memory not set to 0 */
                    _HEAP_REGULAR,      /* regular memory      */
                    expandHeap,     /* function to expand heap */
                    shrinkHeap);    /* function to shrink heap */
Note:
You can use the same expand and shrink functions for more than one heap, as long as the heaps use the same type of memory and your functions are not written specifically for one heap.

Expanding a heap

To increase the size of a heap, you add blocks of memory to it by doing the following:

Adding blocks of memory to a heap

You can add blocks of memory to a fixed-size or dynamically-sized heap with _uaddmem. This can be useful if you have a large amount of memory that is allocated conditionally. Like the starting block, you must first allocate memory for a block of memory. This block will be added to the current heap, so make sure the block you add is of the same type of memory as the heap to which you are adding it. For example, to add 64K to fixedHeap:

static char newblock[65536];

_uaddmem(fixedHeap,       /* heap to add to   */
         newblock, 65536, /* block to add     */
         _BLOCK_CLEAN);   /* sets memory to 0 */
Note:
For every block of memory you add, a small number of bytes from it are used to store internal information. To reduce the total amount of overhead, it is better to add a few large blocks of memory than many small blocks.

Writing a heap-expanding function

When you call _umalloc (or a similar function) for a dynamically-sized heap, _umalloc tries to allocate the memory from the initial block you provided to _ucreate. If not enough memory is there, it then calls the heap-expanding function you specified as a parameter to _ucreate. Your function then gets more memory from the operating system and adds it to the heap. It is up to you how you do this.

Your function must have the following prototype:

void *(*functionName)(Heap_t uh, size_t *size, int *clean);

Where functionName identifies the function (you can name it however you want), uh is the heap to be expanded, and size is the size of the allocation request passed by _umalloc. You probably want to return enough memory at a time to satisfy several allocations; otherwise every subsequent allocation has to call your heap-expanding function, reducing your program's execution speed. Make sure that you update the size parameter if you return more than the size requested.

Your function must also set the clean parameter to either _BLOCK_CLEAN, to indicate the memory has been set to 0, or !_BLOCK_CLEAN, to indicate that the memory has not been initialized.

The following fragment shows an example of a heap-expanding function:

static void *expandHeap(Heap_t uh, size_t *length, int *clean)
{
   char *newblock;
   /* round the size up to a multiple of 64K *  /
   *length = (*length / 65536) * 65536 + 65536;  

   *clean = _BLOCK_CLEAN;  /* mark the block as "clean" */
   return(newblock);       /* return new memory block   */
}

Using a heap

Once you have created a heap, you can open it for use by calling _uopen:

 _uopen(fixedHeap);

This opens the heap for that particular process; if the heap is shared, each process that uses the heap needs its own call to _uopen.

You can then allocate and free memory from your own heap just as you would from the default heap. To allocate memory, use _ucalloc or _umalloc. These functions work just like calloc and malloc, except you specify the heap to use as well as the size of block that you want. For example, to allocate 1000 bytes from fixedHeap:

void *up;
up = _umalloc(fixedHeap, 1000);

To reallocate and free memory, use the regular realloc and free functions. Both of these functions always check the heap from which the memory was allocated, so you don't need to specify the heap to use. For example, the realloc and free calls in the following code fragment look exactly the same for both the default heap and your heap:

void *p, *up;
p = malloc(1000);   /* allocate 1000 bytes from default heap */
up = _umalloc(fixedHeap, 1000);  /* allocate 1000 from fixedHeap */

realloc(p, 2000);   /* reallocate from default heap */
realloc(up, 100);   /* reallocate from fixedHeap    */

free(p);            /* free memory back to default heap */
free(up);           /* free memory back to fixedHeap    */

When you call any heap function, make sure the heap you specify is valid. If the heap is not valid, the behavior of the heap functions is undefined.

Getting information about a heap

You can determine the heap from which any object was allocated by calling _mheap. You can also get information about the heap itself by calling _ustats, which tells you:

Closing and destroying a heap

When a process has finished using the heap, close it with _uclose. Once you have closed the heap in a process, that process can no longer allocate from or return memory to that heap. If other processes share the heap, they can still use it until you close it in each of them. Performing operations on a heap after you have closed it causes undefined behavior.

To destroy a heap, do the following:

After you destroy a heap, it is up to you to return the memory for the heap (the initial block of memory you supplied to _ucreate and any other blocks added by _uaddmem) to the system.

Writing the heap-shrinking function

When you call _uheapmin or _udestroy to coalesce or destroy a dynamically-sized heap, these functions call your heap-shrinking function to return the memory to the system. It is up to you how you implement this function.

Your function must have the following prototype:

void (*functionName)(Heap_t uh, void *block, size_t size);

Where functionName identifies the function (you can name it however you want), uh identifies the heap to be shrunk. The pointer block and its size are passed to your function by _uheapmin or _udestroy. Your function must return the memory pointed to by block to the system. For example:

static void shrinkHeap(Heap_t uh, void *block, size_t size)
{
  free(block);
  return;
}

Changing the default heap used in a program

The regular memory management functions (malloc and so on) always use the current default heap for that thread. The initial default heap for all XL C/C++ applications is the runtime heap provided by XL C/C++. However, you can make your own heap the default by calling _udefault. Then all calls to the regular memory management functions allocate memory from your heap instead of the default runtime heap.

The default heap changes only for the thread where you call _udefault. You can use a different default heap for each thread of your program if you choose. This is useful when you want a component (such as a vendor library) to use a heap other than the XL C/C++ default heap, but you cannot actually alter the source code to use heap-specific calls. For example, if you set the default heap to a shared heap and then call a library function that calls malloc, the library allocates storage in shared memory

Because _udefault returns the current default heap, you can save the return value and later use it to restore the default heap you replaced. You can also change the default back to the XL C/C++ default runtime heap by calling _udefault and specifying the _RUNTIME_HEAP macro (defined in umalloc.h). You can also use this macro with any of the heap-specific functions to explicitly allocate from the default runtime heap.

Compiling and linking a program with user-created heaps

To compile an application that calls any of the user-created heap functions (prefixed by _u), specify hu on the -l linker option. For example, if the libhu.a library is installed in the default directory, you could specify:

xlc progc.c -o progf -lhu

Examples of creating and using user heaps

Example of a user heap with regular memory

The program below shows how you might create and use a heap that uses regular memory.

#include <stdlib.h>
#include <stdio.h>
#include <umalloc.h>

static void *get_fn(Heap_t usrheap, size_t *length, int *clean)
{
   void *p; 
   /* Round up to the next chunk size */   
   *length = ((*length) / 65536) * 65536 + 65536;  
   *clean = _BLOCK_CLEAN;
    p = calloc(*length,1);
    return (p);
}

static void release_fn(Heap_t usrheap, void *p, size_t size)
{
   free( p );
   return;
}

int main(void)
{
   void    *initial_block;
   long   rc;
   Heap_t  myheap;
   char    *ptr;
   int     initial_sz;

   /* Get initial area to start heap */
   initial_sz = 65536;
   initial_block = malloc(initial_sz);
   if(initial_block == NULL) return (1);

   /* create a user heap */
   myheap = _ucreate(initial_block, initial_sz, _BLOCK_CLEAN, 
                     _HEAP_REGULAR, get_fn, release_fn);
   if (myheap == NULL) return(2);


   /* allocate from user heap and cause it to grow */
   ptr = _umalloc(myheap, 100000);
   _ufree(ptr);

   /* destroy user heap */
   if (_udestroy(myheap, _FORCE)) return(3);

   /* return initial block used to create heap */

   free(initial_block);
   return 0;
}

Example of a shared user heap - parent process

The following program shows how you might implement a heap shared between a parent and several child processes. This program shows the parent process, which creates the shared heap. First the main program calls the init function to allocate shared memory from the operating system (using CreateFileMapping) and name the memory so that other processes can use it by name. The init function then creates and opens the heap. The loop in the main program performs operations on the heap, and also starts other processes. The program then calls the term function to close and destroy the heap.

#include <umalloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PAGING_FILE  0xFFFFFFFF
#define MEMORY_SIZE  65536
#define BASE_MEM     (VOID*)0x01000000

static HANDLE hFile;       /* Handle to memory file                       */
static void*  hMap;        /* Handle to allocated memory                  */

typedef struct mem_info {
   void  * pBase;
   Heap_t  pHeap;
} MEM_INFO_T;

 /*------------------------------------------------------------------------*/
 /* inithp:                                                                */
 /* Function to create and open the heap with a named shared memory object */
 /*------------------------------------------------------------------------*/
static Heap_t inithp(size_t heap_size)
{
   MEM_INFO_T info;        /* Info structure               */

   /* Allocate shared memory from the system by creating a shared memory   */
   /* pool basing it out of the system paging (swapper) file.              */

   hFile = CreateFileMapping( (HANDLE) PAGING_FILE, NULL, PAGE_READWRITE, 0,
                                heap_size + sizeof(Heap_t), "MYNAME_SHAREMEM" );
   if (hFile == NULL) {
     return NULL;
   }

   /* Map the file to this process' address space, starting at an address  */
   /* that should also be available in child processe(s)                   */

   hMap = MapViewOfFileEx( hFile, FILE_MAP_WRITE, 0, 0, 0, BASE_MEM );

   info.pBase = hMap;
   if (info.pBase == NULL) {
     return NULL;
   }

   /* Create a fixed sized heap.  Put the heap handle as well as the      */
   /* base heap address at the beginning of the shared memory.            */

   info.pHeap = _ucreate((char *)info.pBase + sizeof(info), heap_size - sizeof(info),
                 !_BLOCK_CLEAN, _HEAP_SHARED | _HEAP_REGULAR, NULL, NULL);

   if (info.pBase == NULL) {
     return NULL;
   }

   memcpy(info.pBase, info, sizeof(info));

   if (_uopen(info.pHeap)) {      /* Open heap and check result           */
     return NULL;
   }

   return info.pHeap;

}

/*------------------------------------------------------------------------*/
/* termhp:                                                                */
/* Function to close and destroy the heap                                 */
/*------------------------------------------------------------------------*/
static int termhp(Heap_t uheap)
{
   if (_uclose(uheap))                       /* close heap                */
      return 1;
   if (_udestroy(uheap, _FORCE))             /* force destruction of heap */
      return 1;

   UnmapViewOfFile(hMap);                    /* return memory to system   */
   CloseHandle(hFile);

   return 0;
}

/*------------------------------------------------------------------------*/
/* main:                                                                  */
/* Main function to test creating, writing to and destroying a shared     */
/* heap.                                                                  */
/*------------------------------------------------------------------------*/
int main(void)
{
   int i, rc;                            /* Index and return code         */
   Heap_t uheap;                         /* heap to create                */
   char *p;                              /* for allocating from heap      */

   /*                                                                     */
   /* call init function to create and open the heap                      */
   
   uheap = inithp(MEMORY_SIZE);
   if (uheap == NULL)                    /* check for success             */
      return 1;                          /* if failure, return non zero   */

   /*                                                                     */
   /* perform operations on uheap                                         */
   /*                                                                     */
   for (i = 1; i <= 5; i++)
   {
      p = _umalloc(uheap, 10);           /* allocate from uheap           */
      if (p == NULL)
         return 1;
      memset(p, 'M', _msize(p));         /* set all bytes in p to 'M'     */
      p = realloc(p,50);                 /* reallocate from uheap         */
      if (p == NULL)
        return 1;
      memset(p, 'R', _msize(p));         /* set all bytes in p to 'R'     */
   }

   /*                                                                     */
   /* Start a second process which accesses the heap                      */
   /*                                                                     */
   if (system("memshr2.exe"))
     return 1;

   /*                                                                     */
   /* Take a look at the memory that we just wrote to. Note that memshr.c */
   /* and memshr2.c should have been compiled specifying the              */
   /* alloc(debug[, yes]) flag.                                           */
   /*                                                                     */
    #ifdef DEBUG
      _udump_allocated(uheap, -1);
    #endif

   /*                                                                      */
   /* call term function to close and destroy the heap                     */
   /*                                                                      */
   rc = termhp(uheap);

   #ifdef DEBUG
     printf("memshr ending... rc = %d\n", rc);
   #endif

   return rc;
}

Example of a shared user heap - child process

The following program shows the process started by the loop in the parent process. This process uses OpenFileMapping to access the shared memory by name, then extracts the heap handle for the heap created by the parent process. The process then opens the heap, makes it the default heap, and performs some operations on it in the loop. After the loop, the process replaces the old default heap, closes the user heap, and ends.

#include <umalloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static HANDLE hFile;       /* Handle to memory file                       */
static void*  hMap;        /* Handle to allocated memory                  */

typedef struct mem_info {
   void  * pBase;
   Heap_t  pHeap;
} MEM_INFO_T;

/*------------------------------------------------------------------------*/
/* inithp:  Subprocess Version                                            */
/* Function to create and open the heap with a named shared memory object */
/*------------------------------------------------------------------------*/
static Heap_t inithp(void)
{

   MEM_INFO_T info;                       /* Info structure               */

   /* Open the shared memory file by name.  The file is based on the      */
   /* system paging (swapper) file.                                       */

   hFile = OpenFileMapping(FILE_MAP_WRITE, FALSE, "MYNAME_SHAREMEM");

   if (hFile == NULL) {
     return NULL;
   }

   /* Figure out where to map this file by looking at the address in the   */
   /* shared memory where the memory was mapped in the parent process.     */

   hMap = MapViewOfFile( hFile, FILE_MAP_WRITE, 0, 0, sizeof(info) );

   if (hMap == NULL) {
     return NULL;
   }

   /* Extract the heap and base memory address from shared memory          */

   memcpy(info, hMap, sizeof(info));
   UnmapViewOfFile(hMap);

   hMap = MapViewOfFileEx( hFile, FILE_MAP_WRITE, 0, 0, 0, info.pBase );

   if (_uopen(info.pHeap)) {          /* Open heap and check result       */
      return NULL;
   }

   return info.pHeap;
}

 /*------------------------------------------------------------------------*/
 /* termhp:                                                                */
 /* Function to close my view of the heap                                  */
 /*------------------------------------------------------------------------*/
 static int termhp(Heap_t uheap)
 {
    if (_uclose(uheap))                       /* close heap                */
       return 1;

    UnmapViewOfFile(hMap);                    /* return memory to system   */
    CloseHandle(hFile);

    return 0;
 }

/*------------------------------------------------------------------------*/
/* main:                                                                  */
/* Main function to test creating, writing to and destroying a shared     */
/* heap.                                                                  */
/*------------------------------------------------------------------------*/
int main(void)
{
   int rc, i;                      /* for return code, loop iteration    */
   Heap_t uheap, oldheap;          /* heap to create, old default heap   */
   char *p;                        /* for allocating from the heap       */

   /*                                                                    */
   /* Get the heap storage from the shared memory                        */
   /*                                                                    */
   uheap = inithp();
   if (uheap == NULL)
     return 1;

   /*                                                                    */
   /* Register uheap as default runtime heap, save old default           */
   /*                                                                    */
   oldheap = _udefault(uheap);
   if (oldheap == NULL) {
      return termhp(uheap);
}

   /*                                                                    */
   /* Perform operations on uheap                                        */
   /*                                                                    */
   for (i = 1; i <= 5; i++)
   {
      p = malloc(10);     /* malloc uses default heap, which is now uheap*/
      memset(p, 'M', _msize(p));
   }

   /*                                                                    */
   /* Replace original default heap and check result                     */
   /*                                                                    */
   if (uheap != _udefault(oldheap)) {
      return termhp(uheap);
}

   /*                                                                    */
   /* Close my views of the heap                                         */
   /*                                                                    */
   rc = termhp(uheap);

   #ifdef DEBUG
     printf("Returning from memshr2 rc = %d\n", rc);
   #endif
   return rc;

}