The zone memory system is an internal memory allocator used by the Doom engine for memory management. Due to technology at the time of Doom's release, memory allocation was considered an expensive operation. Presumably due to the inadequacies of memory management functions available in DOS at the time of Doom's development, Doom includes its own memory allocator.
In a normal C program, memory is allocated and deallocated using the C functions malloc and free. The zone memory code contains its own implementations of these, Z_Malloc and Z_Free.
Rather than calling low-level memory management routines such as sbrk, zone heaps allocate a single, large, contiguous block of RAM using the normal system malloc at the beginning of execution and then divide this large block into smaller ones, keeping all the blocks linked together in a list. When two or more free blocks touch each other, the blocks are merged together to keep the list length short. This helps keep search times for a free block of memory shorter and prevents unnecessary external fragmentation (if blocks are too small, no future allocation may be able to fit into them).
Domain-specific allocation lifetimes
The Z_Malloc function differs from the C malloc function in that it includes support for "tags." Each piece of memory has a particular tag which indicates its purpose and determines its allocation lifetime. Memory blocks are treated differently according to their tags.
The simplest tag is the PU_STATIC tag which specifies a piece of memory which must be explicitly deallocated with the Z_Free function. From a programmer's point of view, using the functions in this way is identical to using C's memory functions.
More interesting is the PU_CACHE tag. Memory allocated with this tag may be automatically freed back to the system if it runs out of memory. In this way, caching of WAD file data is possible. It is common, for example, to load data from a WAD resource with the PU_STATIC tag, and when operations on that data have completed, change the tag of the area of memory (using the Z_ChangeTag function) to PU_CACHE. If the system is low on memory, the data may be freed, but otherwise it will be kept in memory for future use. The WAD loading code interacts with the zone memory system to provide this capability, and it was mostly important in Doom as a means to reduce disk reading time.
The other interesting tag is the PU_LEVEL tag. All memory related to the current game level is allocated with the PU_LEVEL tag. When the level exits, the zone memory is searched and all pieces of memory allocated with this tag are freed. This relieves the burden of having to individually free each piece of memory related to the level.
The zone memory system also augmented Doom by providing advanced debugging and diagnostics features that are not available through the standard library memory routines. Amongst these are some of the following:
- Sentinel words to guard against buffer overflows. These are the source of the dreaded "Freed a pointer without ZONEID" messages caused by poorly made maps. All memory blocks have the ZONEID (0x1d4a11 in the original source) written into their header, and if it is wrong during any memory operation, the game engine knows that its heap has been corrupted.
- Complete heap verification. The engine can stop to trace its way across the entire heap and verify that everything is correctly arranged. This is done after extensive operations such as loading save games.
- Ability to dump the heap to a text file to look for errors or for ways to improve memory allocation.
The file z_zone.h in the Doom source code contains an anecdote that John Carmack considered the zone memory system in Doom "the only stuff that might have been useful for Quake". While it was in fact used in the Quake engine, in a modified form, the WAD code was additionally used as the basis for Quake's BSP files.
The zone allocator is loosely based on id Software's earlier memory manager module for 16-bit programs, which originated in the Hovertank 3D code base and was used in the majority of id games created afterward. This early version of the system also supported purgable blocks, owner pointers, and tags, but was significantly more complicated due to the multiple types of addressable memory areas in real mode.
- MEMMGR.C source code file in the Flat Rock Software GitHub repository.