Apple, the Apple logo, and Macintosh are registered trademarks of Apple Computer, Inc.
Mac and OpenDoc are trademarks of Apple Computer, Inc.
The OpenDoc Memory Manager is a fast and space-efficient memory allocator. It was originally developed as part of the Bedrock project, and has been used in a slightly earlier form by MacApp 3.1.
Despite the name, it is not tied to OpenDoc. OpenDoc uses it, but other unrelated software can, too. The Memory Manager’s only requirement is a procedural shared library mechanism like CFM (Macintosh) or DLL (Windows).
The Memory Manager is aware of (but not dependent on) System Object Model™ (SOM). When both are installed, the Memory Manager takes over SOM’s memory management routines, so calls to SOMMalloc, SOMFree, etc. will use the Memory Manager.
Memory Heaps
A heap is a space in which blocks of memory can be allocated. All blocks allocated by the Memory Manager (other than handles) come from a heap. When the Memory Manager initializes itself, it creates a heap for you; but you can create other heaps if you want to. Heaps can also be deleted, and deleting a heap with blocks still in it is both legal and faster than deleting all the blocks individually.
There is always a default heap. Memory allocation calls that don’t explicitly specify a heap will use the current default heap. (If you use SOM, this includes SOM’s memory management calls.) You can change the default heap at any time.
All storage originally comes from the operating system’s memory manager. Heaps get their memory from the OS in large chunks (typically 32k or greater) and then subdivide these chunks as needed as blocks are allocated. When a heap runs out of room, it asks for another chunk, and when all blocks in a chunk are freed, the entire chunk is returned to the OS.
Using multiple heaps won’t make quite as efficient use of memory as storing everything in one heap (since free memory in one heap is not available to another) but allocating a heap for temporary use and then deleting the heap when done can help reduce memory fragmentation, since deleting the heap leaves a small number of large free blocks, rather than a large number of small ones.
struct MemHeap;
MemHeap is the data type that represents a heap. It’s an opaque data type: no internal structure is visible to you, the client. You refer to MemHeaps only via pointers, and you can operate on heaps only via the Memory Manager API.
enum MMHeapLocation {
kMMSysMemory,
kMMAppMemory,
kMMTempMemory
};
Memory for a heap can come from one of three places. System memory is shared between all processes on the system. Application memory is local to the current process (document.) Temporary memory is like application memory, but on the Macintosh comes from a shared pool of memory available to applications, rather than from the application’s fixed-size partition. On other platforms, application and temporary memory are probably identical; but it’s better to specify temporary memory in cross-platform code since, on the Macintosh, very little application memory is available to an OpenDoc process.
Creates a new heap with a given location. initialSize bytes are allocated (slightly less may be available due to internal overhead.) Whenever the heap runs out of space, it will request growBy more bytes from the OS. (The growBy value should be at least 32K or you will get a non-fatal warning. This is a bug and will be fixed later.)
If the C-style string name is not NULL, it will be used to name the heap. The heap’s name can be accessed later by calling MMGetHeapInfo.
If there is not enough memory to create the heap, NULL will be returned.
void MMDisposeHeap( MemHeap* );
Disposes a heap, returning to the OS all the memory it has allocated. As a result, all blocks in the heap, and pointers to those blocks, become invalid.
It is safe to pass NULL to this routine. Nothing will happen.
MemHeap* MMGetDefaultHeap( );
Returns a pointer to the current default heap, the heap in which memory is allocated if you don’t explicitly specify a heap.
MemHeap* MMSetDefaultHeap( MemHeap* );
Makes the given heap the default. Returns a pointer to the previously-default heap, so you can restore it later with another call to MMSetDefaultHeap.
Allocating Blocks of Memory
Memory is allocated from a heap in nonrelocatable blocks. (Relocatable blocks, or handles, are also available; see below.) The API for creating and operating on these blocks is similar to the ANSI C (or SOM) memory API. In fact, the SOM memory calls are set up to point to these routines, so e.g. calling SOMMalloc is identical to calling MMAllocate.
MMBlock MMAllocate( size_t size );
MMBlock MMAllocateClear( size_t size );
Allocates a new block of the given size from the default heap, and returns a pointer to it. MMAllocateClear also fills the block with zeros.
If there is not enough memory to allocate the block (even by expanding the heap), NULL will be returned.
The largest block that can be allocated is 0xFFFFFF, or 16 megabytes.
These alternate calls let you specify the heap from which the block should be allocated.
MMBlock MMReallocate( MMBlock, size_t size );
Changes the size of an already-allocated block, and returns a pointer to the new location of the block. Most likely, this will require allocating a new block of the desired size and copying the contents of the old block (or as much as will fit, if the block is shrinking) into the new block.
Returns NULL if there was not enough memory to expand the block. If the new size is less than or equal to the old size, NULL will never be returned; at worst, the block will be shrunk in place.
void MMFree( MMBlock );
Frees (disposes) a previously allocated block. It is safe to pass NULL to this routine: nothing will happen.
size_t MMBlockSize( MMBlock );
Returns the size of the block. This is the physical size, the amount of memory actually allocated for the block. It is rounded up to a multiple of four and may be up to twelve bytes larger than the logical size that you requested when allocating the block. This discrepancy may be fixed in the future.
MemHeap* MMGetHeap( MMBlock );
Returns a pointer to the heap that owns the block, or NULL if the input pointer does not point to a valid block.
Sets or queries the is-object flag of the block. If the flag is set, it will thenceforth be assumed that the block contains a valid SOM object. Some of the memory debugging calls (q.v.) make use of this flag, and you can also use it for your own purposes.
You should clear this flag before freeing the block since, in the debug build of the memory manager, MMFree will warn you if it is called on an object block.
SOM objects that inherit from ODObject (the OpenDoc root object class) automatically set the is-object flag when created and clear it when deleted. In debug builds of OpenDoc, they also warn if the flag is not set when they’re deleted.
Allocating Relocatable Blocks (Handles)
For convenience, the Memory Manager also provides operations for allocating relocatable blocks, referenced via handles. These blocks are allocated directly by the OS, not by the Memory Manager, and they don’t live inside heaps. However, you can still specify the same types of locations.
Since relocatable blocks are allocated by the OS, an MMHandle is the same as an OS handle and can be passed to OS routines that take handles; likewise, a handle allocated by an OS routine can be passed to any of the routines below that take a MMHandle.
On some platforms, like the Macintosh, a handle is just a pointer to a pointer to a block, and the data in the block can be accessed at any time simply by doubly-dereferencing the handle. This is not cross-platform! If you do this, your code will not work on platforms (like Microsoft Windows) that have a more opaque notion of a handle. Use the pointer returned by MMLockHandle instead to be cross-platform.
MMHandle MMAllocateHandle( size_t );
Allocates a new relocatable block and returns a handle to it. The source of the block (system, application or temporary) is that of the current heap, even though the block is not actually allocated inside that heap.
If the block could not be allocated, NULL is returned.
Allocates a new relocatable block, from the given source, and returns a handle to it.
If the block could not be allocated, NULL is returned.
void MMFreeHandle( MMHandle );
Frees a previously-allocated block given a handle to it. It’s safe to pass NULL to this routine: nothing will happen.
MMHandle MMCopyHandle (MMHandle );
Makes an exact copy of a block and returns a handle to the copy, or NULL if the copy could not be allocated.
size_t MMGetHandleSize( MMHandle );
Returns the size of the block.
MMBoolean MMSetHandleSize( MMHandle, size_t );
Changes the size of the block. If the new size is greater than the old size, extra space will appear at the end, whose contents are unspecified.
Returns true if the operation succeeded, false if it failed. (Shrinking a block always succeeds unless the handle passed in is bogus or the heap is corrupted.)
void* MMLockHandle( MMHandle );
Locks the block, which prevents it from being relocated by the OS in response to other memory requests. Also returns a direct pointer to the contents of the block; it’s safe to dereference this pointer and work on the block’s contents until the block is unlocked. This is the only cross-platform way to operate on the contents of a block.
void MMUnlockHandle( MMHandle );
void MMUnlockPtr( void* ptr );
Unlocks a block, given either a handle to the block or a pointer to its contents. Calls to Lock and Unlock do not nest! The first call to Unlock will unlock the block (and invalidate any pointers to its contents) no matter how often Lock was called.
Checking Free Space
There are two routines you can use to see how much room is available for allocation in a heap.
Returns the total free space available in the given heap, including any space the heap could make available by growing. (Note that for a typical heap that's allocating out of temporary memory, this represents the total amount of free space available on the entire machine.) On return the value pointed to by total will contain the total free space, and the value pointed to by contig will contain the size of the largest single block that could be allocated. If you are not interested in one of these return values, you may pass NULL for the parameter.
Returns the total free space available in the given location (system memory, application memory, or temporary/shared memory.) Note that this does not include any memory being used by OpenDoc heaps in those locations. On return the value pointed to by total will contain the total free space, and the value pointed to by contig will contain the size of the largest single block that could be allocated. If you are not interested in one of these return values, you may pass NULL for the parameter.
Debugging Utilities
The debug configuration of the Memory Manager provides several utility functions you can use to help debug your code’s memory management. Using these routines, you can detect whether you are passing illegal values to the Memory Manager, or overwriting heap data outside of blocks, or whether a given block is valid. You can also collect statistics on a heap as a whole or all blocks in a heap.
In addition to these extra routines, the debugging build also intrinsically does more internal checking of function parameters and data structures; this makes it slower but better able to detect problems. It also pads blocks with known values (Ascii 'PPPP' before a block, 'SSSS' after) and when validation is on will verify these values to make sure they haven't been overwritten by errant code that wrote past the end or before the start of a block. This adds additional overhead (16 bytes) to the size of a block.
Warning: The debugging utilities try to display messages describing explicitly what's wrong (via DebugStr on the Mac.) However, there are some cases where, if an individual block or the heap as a whole is badly damaged, they will crash instead. Such a crash should be interpreted as an indication that something has severely damaged the heap, rather than as a bug in the memory manager. Turning on heap checking (see below) may enable the memory manager to detect the error earlier before it becomes as serious.
long MMConfiguration( );
Returns a 32-bit value describing the current memory manager configuration. At present, only the least significant bit of this number has any meaning: it is a Boolean value that tells whether or not the memory manager debugging features are available. You should check this before making any of the other debugging calls; if debugging is not available these calls will not crash but will not return any useful information either.
void MMBeginMemValidation( );
void MMEndMemValidation( );
Turns memory validation on and off. These calls nest. When validation is on:
• Newly-allocated blocks are filled with 0xBB (read: “Born”.)
• Freed blocks are filled with 0xDD (read: “Dead”.)
• Calls that take block pointers as parameters verify that the block is valid; if it isn’t, they warn you (typically via a low-level debugger) and the operation fails.
Memory validation can also be turned on and off via the ODDebug menu in debugging builds of OpenDoc. (In the Macintosh implementation, this is a sub-menu of the Apple menu.)
Note that when validation is on, crashes may occur when illegal operations happen: that’s part of its purpose. For instance, dereferencing a pointer read from a disposed block will probably cause a crash since the block has been overwritten with 0xDD.
In debug builds of OpenDoc, memory validation is turned on by default.
void MMBeginHeapChecking( );
void MMEndHeapChecking( );
Turns heap checking on and off. These calls nest. Heap checking is like memory validation, but in addition to the tests above, most Memory Manager calls scan through the heap they operate on to verify that the heap’s internal structure is intact and valid. if it isn’t, they warn you (typically via a low-level debugger) and the operation fails.
Heap checking can be very slow, especially if there are large numbers of blocks in the heap. (Actually it's not too bad on a PowerMac 8100/100 or better...) Memory-intensive operations like opening or closing a Bento container can take tens of times longer than normal. On the other hand, heap checking is the best way to track down obscure bugs that destroy heaps. Be patient.
Heap checking can also be turned on and off via the ODDebug menu in debugging builds of OpenDoc. (In the Macintosh implementation, this is a sub-menu of the Apple menu.)
MMBoolean MMDoesHeapExist( MemHeap *heap );
Determines whether the given heap is one the Memory Manager knows about. This function will return false for any heap that was allocated by another process; you may still be able to use such a heap, but it’s OS-dependent and probably bad practice.
These calls validate a single block (normal or relocatable.) If the pointer or handle passed in does not reference a valid block, or if the block’s heap is corrupted, you are warned via a low-level debugger and the call returns false. Otherwise it (silently) returns true.
Note that these calls —- especially MMValidateHandle — may sometimes crash if passed a bogus pointer or handle. If you get a crash in one of these routines, you should assume that either the input value is bogus, or the heap is already corrupted.
MMBoolean MMValidateObject( SOMObject *o );
Just like MMValidatePtr, but also verifies that the blocks is-object flag is turned on, and also calls SOMIsObject to verify that the block looks like a valid object to SOM.
Checks a single heap, or all known heaps, for consistency. This can be somewhat slow if there are large numbers of blocks. If a heap is corrupted, you are warned via a low-level debugger and the call returns false.
The optional ptr parameter to MMValidateHeap will appear in any warning messages if you provide it; this feature is used by MMValidatePtr. Typically you’ll either not use that parameter or just pass NULL.
Lets you examine every allocated block in a heap. The MMBlockInfoProc procedure pointer you provide will be called once for every block, with the following parameters:
block A pointer to the block.
size The size of the block, in bytes.
isObject The value of the block’s is-object flag; if true, the block is (supposedly) a SOM object.
refCon The refCon parameter you passed to MMWalkHeap.
You should return true to continue the walk, or false to stop it.