This chapter explains how to install and get started with SmartHeap. It then proceeds into an in-depth discussion of each of the SmartHeap memory allocation API families. Several more advanced topics in SmartHeap, such as memory pools and error handling, are also examined. Finally, this chapter discusses how to incorporate SmartHeap into your own product for distribution.
The SmartHeap distribution disk or tape contains C and C++ header files, statically linkable library files, sample applications, and source files for the SmartHeap definitions that override malloc and C++ operator new. Some platforms also contain additional files, such as dynamic link libraries (DLLs or shared libraries).
The following files are included on the SmartHeap distribution media for all platforms:
Directory File Description
include smrtheap.h Header file containing all SmartHeap C declarations except malloc.
include smrtheap.hpp Header file containing SmartHeap C++ declarations, including operator new.
Directory File Description
include shmalloc.h Header file containing declarations for SmartHeap ANSI C functions (malloc, etc.).
include heapagnt.h Header file containing all Debug SmartHeap C declarations (heapagnt.h is automatically included by the other SmartHeap header files when MEM_DEBUG is defined).
source shmalloc.c SmartHeap definition for ANSI C (malloc, etc.) functions — provided so you can customize these.
source shnew.cpp SmartHeap definition for operator new — provided so you can customize and/or build a library for non-supported compilers.
source def*.c, Default memory pool definitions
db*.c used by the above malloc and new definitions.
samples Various sample SmartHeap applications — mostly 16-bit Windows specific applications, but the SmartHeap techniques illustrated apply to all platforms.
For the SmartHeap library files names and platform-specific installation instructions, see the Getting Started and Platform Guide.
Important! Please read the ASCII text file readme.txt for information that updates this manual (the file may be named “README” or “READ ME” on some platforms).
This section covers the basics of using SmartHeap. The information here is very important, and is applicable no matter which SmartHeap features you choose to use in your application.
If you plan to use only the ANSI C malloc, calloc, realloc, and free functions, plus C++ global operators new and delete, you don’t need to include SmartHeap header files or make any other changes to your source code to use SmartHeap — just relink, as described in the next section.
Note Simply linking with the SmartHeap Library is sufficient to cause your application to use the SmartHeap versions of C and C++ memory allocation functions. Include SmartHeap header files only if you want to use other SmartHeap APIs, if you want to create memory pools, if you want SmartHeap to include file and line information in error reports, or if you want to use the SmartHeap debugging APIs.
If you want to use other SmartHeap features, you need to include a SmartHeap header file in each of your source modules that references SmartHeap functions:
smrtheap.h contains all SmartHeap C declarations except for the ANSI C functions.
shmalloc.h contains declarations for ANSI C malloc, etc.
smrtheap.hpp contains C++ declarations, including operator new.
shmalloc.h and smrtheap.hpp each include smrtheap.h, so you need only include one of these header files in each of your source files.
#include
<smrtheap.h> /* for SmartHeap C declarations */
or
#include <shmalloc.h> /* for
ANSI C functions */
or
#include <smrtheap.hpp> //
for SmartHeap C++ declarations
The ANSI C declarations are separated from the other SmartHeap function declarations to avoid conflicts with your C compiler’s declarations of these functions. For 16-bit x86 platforms, the declarations will be different if you’re using a memory model other than large.
To set up your application so it uses the SmartHeap versions of memory allocation functions, simply relink with the SmartHeap Library. There are at least two SmartHeap libraries for each compiler supported by each version of SmartHeap:
The Runtime version library, usually named “shlXXXX.lib,” (“libshXXX.a” on UNIX platforms), is highly optimized and contains minimal error checking. (Replace XXXX with the characters that identify your platform and compiler — for the complete file names on your platform, see the Getting Started and Platform Guide.) Use this version for your final production build that you distribute to end users.
The Debug version library, usually named “shlXXXXd.lib,” (“libshXXXd.a” for UNIX, “hawXXX.lib for Windows”), contains extensive error checking, as well as numerous debugging APIs. To catch memory bugs as soon as possible, you should link with the Debug SmartHeap Library during development.
For platforms that support dynamic link libraries (shared libraries or DLLs), there are two additional library files, usually named “shdXXXX.lib” (for the Runtime version) and “shdXXXXd.lib” or “haXXXXXX.lib” (for the Debug version). On UNIX, the runtime library is libshXXX.so and the debug version is libshXXXd.so. These files are for the DLL (as opposed to statically linked) version of SmartHeap. The static and DLL versions support an identical API, so you can freely interchange them at link time without recompiling your source files. You can link any of the four SmartHeap libraries with your DLLs or your EXEs.
Note Please see the Getting Started and Platform Guide for the specific library names for your platform. When information in your Getting Started Guide does not agree with information provided in this Programmer’s Guide, follow the Getting Started Guide.
All versions of the SmartHeap Library override the ANSI C functions malloc, calloc, realloc, and free, and the C++ global operators new and delete. To ensure that you’re linking with the SmartHeap versions of these functions rather than the version in your compiler’s runtime library, make sure the SmartHeap Library appears before the standard compiler runtime library on the linker command line. If the linker complains that it can’t resolve the symbol SmartHeap_malloc, then it’s linking the compiler’s malloc in rather than the SmartHeap definition.
Note If for some reason you don’t want to override your compiler’s malloc function, you must remove the shmalloc object module from the SmartHeap Library (see §2.3.3 for instructions). In this case, you can still have malloc calls go to SmartHeap by including shmalloc.h in each of your source modules — this header file defines malloc as a macro rather than as a function.
This section applies only to 16-bit x86 platforms. The SmartHeap header file contains explicit far declarations for all functions and pointer parameters and return values, so it’s generally memory-model independent.
The DLL versions of SmartHeap support all memory models. The statically-linkable SmartHeap libraries, however, support only the large memory model. This is because SmartHeap contains calls to the far-model C runtime library, which will fail if you link with a small or medium model C runtime library.
If you have SmartHeap override malloc or operator new (the default), then you must use large model even if you use the SmartHeap DLL. To use another memory model, remove the shmalloc and shnew object modules from the SmartHeap Library, and call MemAllocPtr or farmalloc/_fmalloc rather than malloc. Alternatively, you can use the macro version of SmartHeap malloc.
If you’re using the compact memory model, you can also recompile shmalloc.c and shnew.cpp in the compact memory model — you can then use the malloc or new functions without including any SmartHeap header files.
MemRegisterTask initializes the SmartHeap Library. On most platforms, you don’t need to call MemRegisterTask because SmartHeap will initialize itself when you make the first call.
SmartHeap maintains a registration reference count for each task or process. Each time you call MemRegisterTask, this reference count is incremented. If your last call to SmartHeap occurs before your application is ready to terminate, you can call MemUnregisterTask to terminate SmartHeap. MemUnregisterTask decrements the registration reference count by one — when the count is zero, SmartHeap will free any SmartHeap-allocated memory and debugging state associated with the current task or process.
Important! The compiler runtime library cleanup code may execute calls to free after your main() routine returns. Therefore, if you have SmartHeap override malloc/free, it’s generally not a good idea to call MemUnregisterTask if you have SmartHeap override the compiler-supplied malloc and free (it does by default).
Also important! On UNIX platforms, if you link SmartHeap with a multi-threaded application, you must call MemRegisterTask from the first thread before secondary threads call SmartHeap.
Windows 16-bit In the 16-bit Windows version of SmartHeap, the way task registration works for DLLs has changed slightly since SmartHeap version 1.x. MemRegisterTask is now required before termination, but MemUnregisterTask is not. See the Getting Started and Platform Guide for details.
SmartHeap is fully thread-reentrant in most operating systems that support multi-threading. To determine if SmartHeap is thread-safe in your operating system, see the Getting Started and Platform Guide. Also, if the platform-specific information on multi-threading contradicts this section, use the platform-specific information.
Beginning with version 4.0, SmartHeap includes multi-threaded libraries that are thread-enabled as well as thread-reentrant. If you link your application with one of these multi-threaded libs, the SmartHeap default pool will be thread-safe, and it is not necessary to define MEM_POOL_SERIALIZE as described in the following paragraph.
Important! On UNIX platforms, if you create more than one thread, make sure you call MemRegisterTask from the first thread before starting additional threads. This ensures that SmartHeap can initialize synchronization objects before they’re used.
By default, SmartHeap assumes that an application is single threaded. For single-threaded applications, SmartHeap can avoid the overhead of thread synchronization, which makes SmartHeap more efficient. In particular, the default memory pool used by malloc and operator new are generally not thread-safe by default.
Important! If you link SmartHeap with an application that creates more than one thread of execution, you must serialize the default memory pool by defining the following global variable at file scope in your application:
unsigned MemDefaultPoolFlags = MEM_POOL_SERIALIZE;
This definition causes SmartHeap to initialize the default memory pool with the MEM_POOL_SERIALIZE flag, which makes malloc and new thread-safe. If you have more than one thread, you must define this variable even if you call malloc or operator new from only one thread, because the C runtime library calls malloc from multiple threads and expect the calls to be automatically serialized.
If you use a SmartHeap memory pool in more than one thread at a time, supply MEM_POOL_SERIALIZE when you create the pool. If you don’t supply this flag, you must synchronize access to the memory pool yourself.
Each SmartHeap memory pool is a completely isolated data structure. As a result, you can minimize heap contention and maximize concurrency by allocating data referenced in only one thread (at a time) in separate non-serialized memory pools. This technique of separate pools for separate threads can result in significant performance improvements over malloc, particularly on multi-processor platforms.
SmartHeap provides several application program interfaces (APIs) for memory allocation, including standard C and C++ APIs, and specialized APIs that give more control over memory management.
The following table summarizes the SmartHeap allocation APIs:
API |
Advantages |
Disadvantages |
See |
ANSI C |
Fast; ANSI standard; variable-size blocks. |
Some overhead (1-8 bytes per allocation); local fragmentation possible; max block size UINT_MAX. |
§2.3.2 §2.3.3 §4.1.3 |
C++ |
Fast; standard; variable-size blocks; can overload to use any SmartHeap allocator. |
Some overhead (1-8 bytes per allocation); local fragmentation possible; max block size UINT_MAX unless overloaded. |
§2.3.4 §4.1.4 |
Fixed-size |
Fastest API; zero overhead;
no fragmen- |
Suitable only where numerous objects of the same size are required. |
§2.3.5 §4.1.6 |
Pointer-based |
Fast; variable-size blocks; can partition into pools to improve locality, reduce fragmentation, and simplify freeing; ULONG_MAX blocks. |
Some overhead (1-8 bytes per allocation); local fragmentation possible, but manageable with memory pools. |
§2.3.6 §4.1.5 |
Handle-based |
Can be used to reduce or
eliminate fragmen- |
Requires locking/ unlocking memory handles; slowest; more overhead (10-20 bytes per allocation). |
§2.3.7 §4.1.7 |
If your primary concern is speed of learning or minimal changes to your application, then you can use the standard (ANSI C or C++) APIs. To achieve optimal memory utilization and performance, consider using the SmartHeap APIs. If your application is designed in a layered fashion (as required for true portability), there’s no reason why you can’t take advantage of SmartHeap’s specialized allocators in your lower-level layer, without changing any code in the portable, higher layer(s).
Note In practice, you’ll probably want to combine two or more of these APIs for different data structures within your application. For best memory utilization and greatest efficiency, use the fixed-size API for all situations with several kilobytes or more of related allocations of the same size, and use the pointer-based API for all other allocations. This applies either to C or to C++. For C++ applications, you can create both fixed-size allocations and variable-size pointer-based allocations by overloading operator new. See the example for new in §4.2, “Function reference.”
The most straightforward approach for using SmartHeap is to use the ANSI C API (malloc, calloc, realloc, and free). In this case, you don’t even need to include SmartHeap header files or otherwise make your source files specific to SmartHeap — just relink with the SmartHeap Library.
Important! For SmartHeap to successfully override malloc, etc., you must specify the SmartHeap Library before your compiler’s C runtime library on the linker command line. If the linker complains that SmartHeap_malloc is an unresolved external, then you’re not successfully linking SmartHeap’s malloc.
Note SmartHeap always statically links malloc, etc. even if you use a DLL version of SmartHeap. If you’re using SmartHeap in a DLL, you should never export SmartHeap malloc from your DLL to other DLLs or EXEs, because the function needs to refer to a statically linked global variable.
Windows 16-bit In 16-bit Windows, if you call SmartHeap malloc, etc. from a DLL, SmartHeap allocates shared memory that will be freed automatically when the DLL terminates. If you call malloc from an EXE, SmartHeap allocates non-shared memory — the memory is owned by the current Windows task and is freed automatically when that task terminates.
Note For 16-bit x86 versions, SmartHeap can override malloc only in the large memory model. If you want to use SmartHeap malloc in another memory model, you must either use SmartHeap’s macro version of malloc or use _fmalloc/farmalloc, which SmartHeap also overrides if they’re defined by your compiler. See §2.3.3 for instructions.
Here is how you use the ANSI C API:
#include
<stdlib.h>
#include <string.h>
#define TRUE
1
#define FALSE 0
int DisplayError(char *msg, int
num)
{
/* allocate buffer to format message in */
char
*buf = (char *)malloc(strlen(msg)+20);
/* return if
allocation failed */
if (buf == NULL)
return FALSE;
/*
format and display the message */
sprintf(buf, "Error #%d:
%s.", num, msg);
MessageBox(GetFocus(), buf, NULL,
MB_OK);
/* free the buffer */
free(buf);
return
TRUE;
}
By default SmartHeap overrides malloc and the other ANSI C functions. If you want this default behavior, then skip this section. We recommend that you have SmartHeap override malloc to improve overall memory management efficiency and to avoid having two memory managers linked into your application.
Some reasons why you might not want SmartHeap to override malloc include:
You know of a specific conflict between SmartHeap’s malloc and your C compiler’s runtime library.
You’re using a 16-bit x86 version of SmartHeap and want to use a memory model other than large. In this case, the header files supplied with your C compiler won’t match the SmartHeap versions of these functions, because all SmartHeap functions are far and they accept and/or return far pointers.
You’re defining your own version of the malloc function.
You’re using the ANSI source version of SmartHeap, which allocates memory from malloc (and therefore can’t override the malloc function).
To prevent SmartHeap from overriding the malloc function:
Define the int global variable SmartHeap_malloc at file scope in your application.
Either remove the shmalloc object module from the SmartHeap Library, or place your compiler’s runtime library before the SmartHeap Library on the linker command line.
If you’re avoiding overriding malloc due to a compiler runtime library conflict (including a memory model conflict for 16-bit x86), you can still have your calls to malloc go through SmartHeap, if you want. Just include the header file shmalloc.h in each of your source files. shmalloc.h uses macro substitution to replace the ANSI C function names. This technique avoids possible conflicts with your compiler’s runtime library, because the malloc, etc. functions are still the compiler definitions.
Important! If you include both shmalloc.h and stdlib.h or any other header files that declare malloc, you must include shmalloc.h after the other header files. Otherwise, the malloc macro defined in shmalloc.h will be expanded when stdlib.h is included, resulting in a syntax error in that header file.
Caution! To avoid mixing SmartHeap and compiler runtime library versions of these functions, be sure to include shmalloc.h in each of your source modules. For example, if you were to allocate memory using SmartHeap malloc in one module then free the memory with your compiler’s free in another module, you’d be faced with certain disaster.
Note For 16-bit x86 versions of SmartHeap, another alternative to the macro version of malloc is to use the far versions of malloc, which SmartHeap also overrides by default. The far functions are called either _fmalloc or farmalloc — they’re far functions that accept/return far pointers regardless of memory model, just like SmartHeap malloc. See your compiler library documentation to determine which function to use.
Here is a 16-bit Windows example that works in small and medium models:
#include
<windows.h>
/* define malloc, etc. as macros */
#include
<shmalloc.h>
/*
define if shmalloc not linked */
int SmartHeap_malloc;
BOOL
DisplayError(char far *msg, int num)
{
/* allocate buffer to
format message in */
char far *buf =
(char far
*)malloc(lstrlen(msg)+20);
if
(buf == NULL)
return FALSE; /* allocation failed */
/*
format and display the message */
wsprintf(buf, "Error #%d:
%s.", num, msg);
MessageBox(GetFocus(), buf, NULL, MB_OK);
/*
free the buffer */
free(buf);
return TRUE;
}
SmartHeap defines several versions of the C++ new and delete operators. If you use only the standard global new and delete operators, you don’t need to include any SmartHeap header files or make any changes to your source files — just relink with the SmartHeap Library.
To use SmartHeap’s overloaded versions of operator new, or to define your own operators new and delete in terms of SmartHeap, you need to include the SmartHeap C++ header file smrtheap.hpp.
Important! For SmartHeap to successfully override operator new, you must specify the SmartHeap Library before your compiler’s C++ runtime library on the linker command line.
If you have no references to SmartHeap’s overloaded operator new and you’re having trouble getting your linker to resolve to SmartHeap new rather than your compiler’s new, place a reference to the global variable SmartHeap_new inside one of your functions.
For example:
#include
<smrtheap.hpp>
int main()
{
/* This reference to
SmartHeap_new will ensure
that SmartHeap operator new gets
linked in
rather than the C++ compiler's.
*/
SmartHeap_new = 1;
/* . . . */
}
Window 16-bit In 16-bit Windows, if you call SmartHeap operator new from a DLL, SmartHeap allocates shared memory that will be freed automatically when the DLL terminates. If you call new from an EXE, SmartHeap allocates non-shared memory — the memory is owned by the current Windows task and is freed automatically when that task terminates.
If you want to use SmartHeap from a C++ application but don’t want to have it redefine global operators new and delete, then you’ll need to include smrtheap.h rather than smrtheap.hpp in your source modules. To avoid linking in SmartHeap operator new, you’ll need to either place the SmartHeap Library after your C++ compiler’s runtime library on the linker command line, or else remove the object module shnew from the SmartHeap Library.
SmartHeap defines an alternative operator new that uses the placement syntax for specifying a memory pool. You can also define new and delete for your specific classes. You can use both these techniques in any memory model. See the entry for new in §4.2, “Function reference,” for a detailed explanation and examples. The C++ sample application on the SmartHeap distribution media also illustrates these techniques.
Note For 16-bit x86, SmartHeap also overloads far and huge versions of operator new, if your compiler defines them. Large model is recommended for 16-bit platforms. If you use a memory model other than large, you’ll need to link with your compiler’s global operator new rather than SmartHeap’s. To allocate memory from SmartHeap, you’ll need to either use the far/huge versions of operator new or use the placement syntax with operator new to allocate from a SmartHeap memory pool.
Also note SmartHeap also defines special versions of operator new for certain platforms. For example, SmartHeap provides integration with the Borland C++ ObjectWindows Library and the Microsoft Foundation Class Library that each include overloaded operator new definitions. For more information, see the Getting Started and Platform Guide.
The most important factor in choosing an appropriate allocator is the nature of the data structures that the dynamic memory will contain. If the data structure is made up of many small, fixed-size structures (or class objects), as in the case of lists and trees, the most appropriate allocator is the fixed-size allocator. SmartHeap fixed-size allocations involve absolutely no overhead (meaning header information), aren’t subject to fragmentation (because each block allocated is of the same size), and are very fast (fixed-size allocations involve simply popping the head of a linked list).
Use MemPoolInitFS to create a memory pool from which blocks of a fixed size are allocated. Use MemAllocFS to allocate fixed-size blocks from a pool and MemFreeFS to free the fixed-size blocks.
Here’s an example of how to use SmartHeap’s fixed-size allocator:
#include <smrtheap.h>
/*
example list link structure */
typedef struct _Link
{
int
value;
struct _Link *next;
} Link;
/*
Initialize fixed-size memory pool for links. */
MEM_POOL
CreateList(int InitialCount)
{
return
MemPoolInitFS(sizeof(Link),
InitialCount, MEM_POOL_DEFAULT);
}
/*
add a new link to a list */
Link
*InsertLink(MEM_POOL LinkPool,
Link *prev,int val)
{
/*
allocate a new Link */
Link *link = (Link
*)MemAllocFS(LinkPool);
if (link == NULL)
return NULL; /*
allocation failed */
/*
initialize the new link */
link->value = val;
if
(prev)
{
link->next = prev->next;
prev->next =
link;
}
else
link->next = NULL;
return
link;
}
/*
free entire list */
int FreeList(MEM_POOL LinkPool)
{
/*
Freeing the pool frees all memory in the list
in one call:
much faster and less error prone
than freeing each element
individually. */
return MemPoolFree(LinkPool);
}
Note Beginning in version 3.0, for all blocks under 256 bytes in size, SmartHeap automatically uses an algorithm as fast as the fixed-size algorithm, so you seldom need to use the fixed-size API to achieve good performance.
If a data structure requires a one-time allocation of a variable size (for example, an array or temporary buffer), or if the memory block may be changed in size (re-allocated), then a fixed-size allocator isn’t appropriate. SmartHeap’s implementation of ANSI C malloc often is appropriate.
SmartHeap also defines a similar, but more flexible, alternative that is referred to in this manual as the pointer-based API. The pointer-based API permits you to group allocations into memory pools, which:
Facilitates the partitioning of allocations into working sets to improve performance in virtual-memory environments.
Controls fragmentation.
Simplifies the de-allocation of related allocations.
Use MemPoolInit to create a memory pool from which variable-size blocks are allocated. MemAllocPtr allocates a variable-size block from a pool and returns a pointer to the memory. MemReAllocPtr changes the size of a block. Use MemSizePtr to determine the number of bytes allocated. When you’re through with the memory block, use MemFreePtr to return it to the pool.
The pointer-based API can allocate blocks that exceed UINT_MAX bytes in size. Because malloc is defined by ANSI C as accepting a size_t size parameter, and because size_t is a 16-bit integer in 16-bit environments such as Windows 3.x, malloc can only allocate a maximum block size of 64K in those environments. In contrast, SmartHeap’s MemAllocPtr accepts an unsigned long size parameter and can allocate blocks as large as the operating system can allocate.
Note For 16-bit x86 platforms, blocks greater than 64K must be addressed with huge pointers. The offset of such huge blocks allocated from SmartHeap is not zero. So if you allocate a huge array of structures, an individual structure may straddle segment boundaries even if the structure size is a power of two. As a result, you should allocate huge arrays of structures directly from the operating system, so the offset is zero.
Here’s an example of how to use the pointer-based API:
#include
<smrtheap.h>
/* StringPool is a global variable assumed
* here to have been previously initialized
* with MemPoolInit.
*/
extern MEM_POOL StringPool;
int DisplayError(char *msg, int num)
{
/* allocate buffer in which to format msg */
char *buf = (char *)MemAllocPtr(StringPool,
strlen(msg)+20, FALSE);
/* return if allocation failed */
if (buf == NULL)
return FALSE;
/* format and display the message */
sprintf(buf, "Error #%d: %s.", num, msg);
MessageBox(GetFocus(), buf, NULL, MB_OK);
/* free the buffer */
MemFreePtr(buf);
return TRUE;
}
A handle-based memory allocator is useful for minimizing heap fragmentation because, when the heap becomes fragmented, the blocks can be moved. Memory allocators that return direct pointers into the heap (such as all malloc implementations) can’t move memory because that would invalidate the pointers.
SmartHeap provides a handle-based memory allocator that improves on many of the limitations of other handle-based implementations, including those provided by the Mac and by MS Windows:
The Mac handle-based (“relocatable memory block”) implementation provides an efficient “double-indirection” mechanism for referencing the contents of memory identified by handles. However, this mechanism can be error prone, since the Mac OS can move unlocked memory blocks between references. Moreover, Mac memory handles have a lock bit rather than a reference count, so it’s easy to inadvertently unlock someone else’s lock when you unlock a handle. This can be disastrous for the other user of the block.
The Windows handle implementation does provide a lock count, so it’s less error prone than the Mac. However, it doesn’t provide a double-indirection mechanism, so each reference to a handle requires an expensive function call. Also, the Windows API doesn’t include a function to relocate a moveable block to the bottom of the heap — consequently, locking blocks in Windows always creates a fragmenting sandbar in the heap. Even more serious limitations of the Windows implementation are the 64K total heap size imposed by LocalAlloc and the 8,192 handle limitation of GlobalAlloc.
SmartHeap provides the best of both worlds: a reference count for locking handles and an efficient double-indirection mechanism. Moreover, SmartHeap provides a MemFix facility that moves locked blocks completely out of the moveable heap to a separate fixed-block heap. As a result, these locked blocks don’t fragment the moveable heap even if they’re locked for an extended period.
To minimize the chance of errors, SmartHeap provides a MEM_REFERENCE macro that does efficient indirection in the Runtime version of SmartHeap; in the Debug version of SmartHeap, the macro maps to a function that fully validates both the handle and the block that the handle points to.
Since SmartHeap provides what is essentially a superset of the union of Mac and Windows functionality, you can easily port applications from either environment to SmartHeap. You can also easily take advantage of SmartHeap’s additional functionality to improve performance and eliminate memory-related programming errors.
Note The Mac version of SmartHeap also provides a complete emulation of the Mac OS memory management API. Rather than changing the memory management calls in your Mac application, you can include the SmartHeap header file shmac.h in each of your source files to take advantage of SmartHeap’s improved performance and error detection. See the Getting Started and Platform Guide for details.
The handle-based API can be slower than the other SmartHeap APIs because an allocation call can cause a heap compaction. It also requires more overhead than the other SmartHeap APIs because SmartHeap must store the handle table.
If you’re more concerned about memory conservation than performance (meaning space is more important than speed), and if you require large memory blocks of varying sizes, then the handle-based API might be appropriate. In the following situations, the handle-based API will save a considerable amount of memory relative to the other APIs:
You allocate one or more large blocks (for example, arrays) that are repeatedly allocated to larger and larger sizes. If you use a pointer-based API, then each time such blocks grow, a large hole will be left in the heap that can never again be used by that block since the block grows larger each time it is reallocated.
A less extreme but similar situation arises if you allocate/realloc/free many medium-to-large (more than 100 byte) blocks and seldom allocate small blocks that would fill the gaps between the large blocks.
The cases above illustrate fragmentation that wastes a lot of memory. In each case, if you use the handle-based allocator, the heap can be compacted to eliminate these holes, and overall memory consumption can be reduced by 30% or more.
In a virtual-memory operating system, such a space reduction could result in better overall application performance even though the allocation calls are slower, because the heap might fit in available physical memory and avoid swapping. In other words, using the handle-based API can sometimes reduce the size of the application’s working set.
Use MemPoolInit to create a memory pool from which variable-size blocks are allocated. MemAlloc allocates a variable-size block from a pool, returning a handle to the memory. MemLock locks a memory block at a fixed address on the moveable heap, returning a pointer. MemUnlock unlocks a block so that it may be moved by the memory manager.
MEM_REFERENCE is a macro that very efficiently dereferences a handle without the need to lock the block. MemFix moves a block to the fixed heap and MemUnfix moves it back to the moveable heap. MemReAlloc changes the size of a block. Use MemSize to determine the number of bytes allocated. When you’re through with the memory block, MemFree returns it to the pool.
Note To benefit from the handle-based API, you need to allocate moveable blocks (include MEM_MOVEABLE with the memory flags parameter), and keep the blocks unlocked as much of the time as possible. We recommend using a combination of MEM_REFERENCE for brief references to handles and MemFix for references that need to be retained for an extended period. You should avoid MemLock, especially for extended periods, because this causes a fragmenting sandbar in the moveable heap, which defeats the purpose of the handle-based API.
Also note Normally, MemAlloc and MemReAlloc incrementally compact to avoid increasing the size of the memory pool any more than necessary. If you want to control when compaction occurs, specify MEM_NOCOMPACT in calls to MemAlloc and MemReAlloc. Handle-based allocation calls will then be much faster — in fact, just as fast as pointer-based calls. When your application is idle, you can compact the pool with a call to MemPoolShrink.
Like MemAllocPtr, MemAlloc can allocate blocks greater than UINT_MAX in size. Here’s an example of how to use the handle-based API:
#include
<smrtheap.h>
/* StringPool is a global variable assumed
* here to have been previously initialized
* with MemPoolInit.
*/
extern MEM_POOL StringPool;
MEM_HANDLE hStringTable = NULL;
/*
initialize a string table */
int InitStringTable(unsigned size)
{
/* initialize table to size entries */
hStringTable = MemAlloc(StringPool,
MEM_ZEROINIT | MEM_MOVEABLE | MEM_NOCOMPACT,
sizeof(char *) * size);
return hStringTable != NULL;
}
/*
dereference moveable block & update contents */
int SetString(int index, char *value)
{
/* obtain address of table in memory */
char **pStringTable =
(char **)MEM_REFERENCE(hStringTable);
if (!pStringTable)
return FALSE;
/* update string table entry */
pStringTable[index] = value;
return TRUE;
}
/*
perform lengthy operation on string table */
int ProcessStrings(MEM_HANDLE hStringTable)
{
/* fix string table in memory */
char **pStringTable =
(char **)MemFix(hStringTable);
if (!pStringTable)
return FALSE;
/* ... perform lengthy operation,
directly referencing the pStringTable
pointer many times; the pool's moveable
heap may be compacted ... */
/* unfix the table */
MemFix(hStringTable);
return TRUE;
}
SmartHeap memory pools provide a mechanism for partitioning your memory allocations into related groups known as memory pools. Memory pools allow your application to use multiple heaps, each with its own fixed-size, pointer-based, and handle-based areas.
Memory pools also let you define working sets (to reduce swapping activity), control fragmentation, simplify de-allocation of memory, and, on some platforms, allocate shared memory.
Memory managers in all environments use a dynamic region of memory known as a heap to break memory into blocks at the request of the calling application. In the simplest scheme, the caller specifies the desired block size for each allocation, and the memory manager tries to find a large enough block among available free blocks.
Unfortunately, this scheme has some shortcomings:
If the size and extent of allocations overlap (as they will for any application making extensive use of dynamic memory), the heap will become fragmented. The available memory is scattered in pieces that are too small for the application to use, meaning the memory is effectively wasted.
The heap manager must allocate a header with each allocation. This header keeps track of the size of the allocation and whether it’s free, and links the block with the rest of the heap. A header is overhead and, from the point of view of the application, is also wasted memory.
SmartHeap fixed-size memory pools address each of these problems by setting aside a contiguous chunk of memory for sub-allocations, each of the same size. Both fragmentation and overhead are eliminated because all holes (free blocks) are the same size. Performance is improved dramatically because the memory manager no longer needs to search a heap for the best fit: all blocks are the same size, so SmartHeap simply picks the head of the free list.
Memory pools aren’t just for fixed-size blocks, though. Memory pools partition free memory so that one part of your application can use fixed-size blocks of one size, another part can use fixed-size blocks of a second size, and still another part can use variable-size blocks.
Most dynamic data structures (for example, lists and trees) contain many elements of the same size. If you create a fixed-size pool for each such data structure, you’ll ensure the best possible memory utilization and performance.
When you use memory pools to partition allocations, whether of fixed or variable size, you can free all of the allocations in a memory pool with a single function call rather than with calls to free for each individual allocation.
Note Taking advantage of this feature simplifies your code and greatly reduces the incidence of common memory errors, such as double-freeing or leakage (failing to free memory that is no longer in use). For example, you could allocate each element of a linked list from a memory pool and free the entire list simply by freeing the memory pool. You can also free an individual element if you want to delete that element only.
If you have a complex data structure involving multiple block sizes, you can use multiple memory pools to allocate different parts of the data structure, then free each of the memory pools rather than traversing and freeing individual components. For example, suppose you have a linked list in which each link is a C structure and one of the structure fields is a pointer to a variable-size dynamically allocated string. The best way to allocate memory for such a data structure would be to have a memory pool whose fixed-size block size equals the size of the list’s links. The strings could then be allocated as variable-size blocks from the same memory pool. To de-allocate the list, just free the memory pool.
Here is an example of using a memory pool to store a linked list:
#include <smrtheap.h>
/*
example list link structure */
typedef struct Link
{
char *value;
struct _Link *next;
} Link;
MEM_POOL ListPool;
/*
Initialize a memory pool to store
links and strings. */
int CreateList(int InitialLinkCount)
{
/* allocate fixed-size pool for links */
LinkPool = MemPoolInitFS(sizeof(Link),
InitialLinkCount, MEM_POOL_DEFAULT);
return ListPool;
}
/*
add a new link to a list: copy semantics */
Link *InsertLink(Link *prev, char *val)
{
/* allocate a new Link from fixed-size pool */
Link *link = (Link *)MemAllocFS(ListPool);
if (link == NULL)
return NULL; /* allocation failed */
/* initialize the link, allocating string from
variable-size pool */
link->value =
MemAllocPtr(ListPool, strlen(val) + 1, 0);
if (link->value != NULL)
strcpy(link->value, val);
else
{
MemFreeFS(link);
return NULL;
}
if (prev)
{
link->next = prev->next;
prev->next = link;
}
else
link->next = NULL;
return link;
}
/*
free entire list */
int FreeList()
{
/* Freeing a pool frees all memory in one call:
much faster & less error prone than freeing
each link and string individually. */
return MemPoolFree(ListPool);
}
Most modern operating systems support virtual memory, which is good, because virtual memory provides much more memory for your application to use. But you pay for this extra memory with the performance hit of extra disk accesses, which are slow.
You can control the performance impact virtual memory has on your application by improving your application’s locality of reference. In other words, you want to contiguously arrange data items in memory if those items tend to be referenced together. This will cause related data to be swapped to and from disk as a unit.
For example, if you have a linked list in which every element is stored on a different page of memory, then the list has poor locality. Traversing the list will touch a great number of pages and consequently cause a great number of page faults. If, in contrast, your linked list elements were packed onto just a few pages, then it has good locality and can be traversed efficiently even in low physical memory conditions.
You can use SmartHeap memory pools to improve your application’s locality. Memory pools allow you to control your working sets very easily. This is because each memory pool dedicates one or more pages to the allocations from that pool. SmartHeap’s pages are a multiple of, and exactly aligned with, the system pages in the underlying operating system.
You can control the SmartHeap page size for individual pools with MemPoolSetPageSize. This function lets you tune memory pools for optimal paging performance.
SmartHeap packs as many blocks into each page as can fit. Consequently, the blocks in a memory pool tend to be swapped or paged in and out of physical memory together, making consecutive accesses to the blocks in a given pool more efficient.
In contrast, if all of your allocations are from one memory pool (for example, malloc or global operator new), your memory blocks tend to be scattered randomly throughout the address space. This results in a great deal of swapping activity as you traverse your data structures.
Note By carefully considering which data in your program is likely to be referenced consecutively, you can partition your allocations into memory pools to achieve optimal working sets. If you allocate one or more memory pools for each major data structure in your application, you’ll ensure minimal swapping when traversing these data structures.
SmartHeap memory pools give your application greater locality, which improves performance. Memory pools also let your application run in a smaller working set. Because pages are dedicated to one data structure, fewer pages are needed to store that data structure and, as a result, the application can run in less memory — the application effectively has a smaller footprint.
One way to control fragmentation is to use fixed-size memory pools. This eliminates fragmentation, since all blocks are of the same size. See §2.4.1, “Fixed-size memory pools,” for details.
If you require variable-size memory blocks, you can still use memory pools to mitigate (if not eliminate) fragmentation. To achieve this, you need to analyze your variable-size allocations and group them in pools according to their extent (the period from the time they’re allocated to the time they’re freed). To use the following techniques, you need to use the pointer-based API (MemAllocPtr).
Allocate one memory pool for temporary buffers that are freed soon after they’re allocated. This prevents temporary buffers from fragmenting pools that contain more permanent blocks.
If you have a group of blocks that will be allocated in succession, then freed at the same time, allocate them from the same pool so you can free the group with a single call to MemPoolFree (see §2.4.2, “Freeing related allocations,” for an example). Not only will this simplify de-allocation, but it will prevent the blocks in this pool from fragmenting other pools whose blocks are freed at different times.
If fragmentation is a problem in your application, you might also consider using moveable memory blocks. This approach is suitable only when you don’t need to maintain direct pointers to memory and when the blocks you’re allocating are relatively large (generally, 100 bytes or more). For example, if you have a number of arrays of different sizes, you could use the handle-based API (MemAlloc) to allocate each array and call MEM_REFERENCE (which does double-indirection) whenever you want to access an element. You would maintain indices rather than pointers into the array, which is important because the array’s address could change while the array is unlocked.
Note Shared memory is currently supported only for 16-bit and 32-bit Windows OS/2, and UNIX. Please skip this section for other platforms. For further details on using shared memory on your platform, see the Getting Started and Platform Guide. Also, if the platform-specific section on shared memory contradicts this section, use the platform-specific information.
There are two ways to create shared memory. For most platforms only one of the two methods is supported (OS/2 is the exception, where either technique can be used):
Create the memory pool in the usual way, with MemPoolInit or MemPoolInitFS, and include MEM_POOL_SHARED with the flags parameter. This creates unnamed shared memory and is supported only for operating systems that provide unnamed shared memory.
Create named shared memory pools with MemPoolInitNamedShared or MemPoolInitNamedSharedEx. These APIs are supported only on platforms that support named shared memory.
Some operating systems, such as 16-bit Windows, provide only a single address space that is shared between all processes (a process does not have its own address space). For these operating systems, you don’t need to do anything special to access shared memory in various tasks once the shared memory is created. You can freely pass pointers to the shared memory between tasks.
For operating systems that do give each process its own address space, you must call MemPoolAttachShared to explicitly map a shared memory pool into a given process. In these operating systems, from each process that has attached to the shared memory pool, you must call MemPoolFree to end access to the shared pool from that process. When all processes that are using a shared pool have called MemPoolFree, SmartHeap frees the pool.
Shared memory is implemented quite differently on each platform. See the Getting Started and Platform Guide for further platform-specific details of the SmartHeap shared implementation on your platform.
You can control the growth and shrinkage of individual memory pools, which lets you precisely monitor and control the resources consumed by your application.
SmartHeap memory pools normally grow in response to allocation requests, in increments of the page size established by MemPoolSetPageSize. SmartHeap automatically returns a page in a pool to the operating system when all blocks allocated from that page have been freed. If a pool contains a moveable heap (handle-based blocks) and if the pool doesn’t have enough free memory to fulfill an allocation request, SmartHeap will compact the pool before requesting additional memory from the operating system.
You can change default pool sizing in several ways. First, you can use MemPoolPreAllocate to reserve memory for a pool in advance of the allocation requests that will use the memory. You can also call MemPoolShrink to immediately give as much memory from a pool back to the operating system as possible — this will fully compact the moveable heap, if present, then release any pages that have no blocks currently in use.
You can establish upper and lower bounds on a pool’s size with MemPoolSetCeiling and MemPoolSetFloor, respectively. SmartHeap will never request more memory for a pool than its ceiling, even if this means an allocation request will fail. Likewise, SmartHeap never shrinks a pool below its floor, even if there are free pages.
SmartHeap includes several functions for providing information about memory pools. MemPoolSize returns the total system memory allocated by a pool, while MemPoolCount returns the number of outstanding allocations in a pool.
The MemPoolInfo function lets you identify the type and block size of a memory pool, or determine from which pool a pointer was allocated.
You can enumerate, or traverse, all SmartHeap memory pools with the functions MemPoolFirst and MemPoolNext.
MemPoolWalk allows you to enumerate all of the allocations within a particular pool.
Complete details on these and other memory pool functions can be found in §4.1.2.
This section discusses error handling in the Runtime SmartHeap Library — for information about error handling for debugging, see §3.4, “Debug error handling.”
SmartHeap can detect a wide range of errors at run time. You can optionally handle these errors with your own error-handling callback. The Runtime version of SmartHeap detects only those errors that can be detected very inexpensively.
Even in the Runtime version, SmartHeap won’t allow invalid parameters through its API entry points. Therefore, invalid parameters should not be able to corrupt SmartHeap or your heap data. However, in the Runtime version, SmartHeap may fail with a memory access violation if an application passes an invalid address as a parameter. (The Debug version of SmartHeap performs full address validation of all parameters at each entry point, but this would degrade performance in the Runtime version.) It’s your responsibility to use a signal handler or other exception handler to handle exceptions that result from invalid parameters.
Runtime SmartHeap can detect the following error conditions:
You run out of available operating system memory.
You pass an invalid memory pool, handle, or pointer to a SmartHeap function.
You attempt an allocation of size zero (legal only for C++ operator new).
You attempt to reallocate a block in place, but there is insufficient free memory following the block to expand in place.
You exceed a pool’s ceiling, as established by MemPoolSetCeiling.
You attempt to set a pool’s fixed-size block size larger than its page size.
You overflow/underflow the lock count with MemLock or MemFix.
For details on what causes these errors (including which function is responsible for an error) and on the SmartHeap error codes that the errors generate, see §4.3, “Error codes.”
You can establish an error-handling callback function that gets control whenever SmartHeap detects an error at run time. The error handler acts much like the new_handler of C++, except that it can handle many errors in addition to out-of-memory conditions (see §4.3, “Error codes,” for a complete list of error conditions and their causes).
When the Runtime version of SmartHeap detects an error, it passes the error code and the memory pool in which the error occurred to the error handling function of the current task or process.
The SmartHeap default error handler is the only place where SmartHeap is exposed to the end user in the user interface. We suggest you implement a memory error handler in your production application so you can fully control your application’s user interface.
If you don’t define an error handler, SmartHeap’s default error handler will be in effect. This handler, MemDefaultErrorHandler, displays a prompt indicating the type of error:
If the operating system ran out of memory, the default error handler includes a “Retry” option so that the end user can try to free some memory. If the user chooses “Retry”, SmartHeap will attempt the allocation again. If SmartHeap is successful, your program will be able to proceed normally.
If the error was one of the other conditions listed in §2.5.1, then the error is a programming error. In this case, the default error handler in the Runtime version of SmartHeap issues a terse message identifying only the error type. You can use the Debug version of SmartHeap to diagnose such errors, and you can install your own error handler to handle them in the Runtime version.
o clear the error handler so no handling of runtime errors occurs, pass NULL to the MemSetErrorHandler function. In this case, the SmartHeap API that detected the error will fail and return an appropriate failure return value (see §4.2 for the return values of each SmartHeap function).
Note If you carefully define an error handler that causes a non-local exit (for example, Throw), you can avoid testing the result of every call to an allocation function. This makes programming more convenient, reduces the chances for programming error, reduces overall code size, and produces more readable code. Note that if you non-local exit from an error handler, you must first inform SmartHeap by calling MemErrorUnwind.
Complete details on defining an error handler, including an example of one that non-local exits, can be found under MemSetErrorHandler, in §4.2.
When you’re ready to distribute an application that uses SmartHeap, keep the following points in mind:
The Runtime SmartHeap DLL or shared library, if present, is the only file that your license permits you to distribute.
You may also distribute any executables that include object code from the SmartHeap Runtime version library (but not the Debug version library).
The SmartHeap include files, library files, sample files, Debug version DLLs and source files may not be distributed to more than one party for any reason.
If you redistribute the SmartHeap DLL with your application, your install procedure should check the DLL version to ensure that you don’t overwrite a newer version that another application may have installed. To verify the SmartHeap version number, use the MemVersion API — see the example for MemVersion in §4.2, “Function reference.” The 16-bit Windows version of the SmartHeap DLL also includes a version-identification resource. See your Windows SDK documentation on VER.DLL for information about version resources.
Windows 16-bit · When the 16-bit Windows SmartHeap DLL is initialized, it spawns a background task, sh30mon.exe, to monitor task termination. If sh30mon.exe doesn’t exist, it’s automatically created by the SmartHeap DLL. However, if the SmartHeap DLL is installed on a network with read-only access, then SmartHeap will be unable to create the EXE file and will thus fail to initialize. Therefore, you may want to distribute sh30mon.exe along with the SmartHeap DLL.
SmartHeap
Programmer’s Guide