C Memory Management — Section 3.11¶
This article synthesises Section 3.11 ("Memory Management") of Amiga C for Advanced Programmers by Bleek, Jennrich, Schulz (Abacus / Data Becker, ~1991). It covers the memory model the C programmer must understand on the Amiga: AllocMem / FreeMem, the MEMF_* flags, the MEMF_CHIP constraint, and the difference between static and dynamic memory.
For the modern comprehensive treatment, see exec/memory-pools.md, exec/exec-library-reference.md, and library-reference/exec-library.md.
Editorial note: page numbers in this article refer to the PDF page index in the raw OCR source.
Why memory management matters on the Amiga (page 379)¶
The book's section 3.11 opens by illustrating the problem:
"The program could need a variable array double the size of the number expected for program execution. This could have a dimension of 1000×1000 for the color values of a high resolution graphic. Our compiler reserves a doubled variable in the finished program 100000 times. You can imagine how much memory that requires."
The C programmer on the Amiga must understand:
- Where the memory comes from (the exec allocator)
- What kind of memory to ask for (CHIP, PUBLIC, FAST)
- When to free it (before exiting or losing the handle)
- How to handle failure (memory allocation can fail)
The MEMF_* flags (page 380)¶
Amiga memory is tagged with attribute flags. The major ones (per <exec/memory.h>):
| Flag | Meaning |
|---|---|
MEMF_ANY |
Any memory the allocator has — default |
MEMF_PUBLIC |
Memory accessible by CPU and all DMA devices |
MEMF_CHIP |
Memory in the first 512 KB + 512 KB (chip-RAM) — accessible by all custom-chip DMA |
MEMF_FAST |
Memory outside chip-RAM (32-bit-only on 68020+) — accessible by CPU only |
MEMF_CLEAR |
Zero-initialised |
MEMF_REVERSE |
Allocate from the high end (useful for stack-like patterns) |
MEMF_LOCAL |
V39+ — CPU-local on systems with multiple processors |
MEMF_31BIT |
V39+ — addressable in 31-bit address space (for cards above 16 MB) |
MEMF_KICKBACK |
V40+ — diagnose who allocated |
The crucial distinction:
- CHIP memory is needed for: bitmaps, audio samples, sprite data, copper lists — anything the custom chips DMA
- FAST memory is fine for: code, stack, regular data, blitter source/dest patterns
- PUBLIC memory is the default; it can be either CHIP or FAST, wherever the allocator finds space
On a stock A500 / A1000 / A2000 with 512 KB chip RAM, all memory is CHIP and the distinction doesn't matter. On an A1200 (2 MB chip + 8 MB fast) or an accelerated system with fast RAM, the distinction is critical.
The book has a rule of thumb: "Use CHIP memory only for things the custom chips need." The modern equivalent lives in
conventions/coding-standards.mdunder "Memory rules".
Allocating memory (page 382)¶
#include <exec/memory.h>
/* Allocate 1024 bytes of zero-initialised memory (any kind) */
APTR mem = AllocMem(1024, MEMF_ANY | MEMF_CLEAR);
if (mem == NULL) {
/* allocation failed — handle it */
return ERROR_NO_MEMORY;
}
The signature is:
APTR AllocMem(ULONG byteSize, ULONG requirements);
AllocMem is synchronous — it returns either a valid pointer or NULL. It does not block.
For zero-initialisation, use MEMF_CLEAR — the allocator will zero the bytes before returning.
V39+ alternative:
AllocVec(size, requirements)is the vec variant that records the allocation size so you don't have to remember it forFreeVec. PreferAllocVecin new code.
Freeing memory (page 385)¶
FreeMem(mem, 1024); /* the classic way */
FreeVec(mem); /* if allocated with AllocVec */
You must match the size you passed to AllocMem. Mismatching the size is a memory-corruption bug:
- If you pass a smaller size, only part of the block is freed; the rest leaks.
- If you pass a larger size, you may free memory that doesn't belong to you.
This is why AllocVec / FreeVec is preferred: the size is stored in the memory block header.
Memory pools (page 388)¶
For programs that allocate many small blocks, individual AllocMem calls are expensive (each one crosses the exec allocator boundary). The book introduces puddle-and-threshold memory pools (covered more thoroughly in exec/memory-pools.md):
#include <exec/memory.h>
struct MemList *pool = AllocPooled(mem, 4096); /* sub-allocate from a parent block */
char *buf = AllocPooled(pool, 100); /* sub-allocate within the pool */
/* Free everything in one go */
FreePooled(pool);
Pools are useful when: - You allocate many related buffers that share a lifetime - You want O(1) cleanup - You want to avoid the exec allocator overhead
Special: MEMF_CHIP for graphics and audio (page 390)¶
The book devotes extra attention to chip memory because it's the most error-prone on the Amiga. Rules:
/* MUST be chip memory: */
struct BitMap *bm = AllocMem(sizeof(struct BitMap) + ... , MEMF_CLEAR | MEMF_CHIP);
WORD *audio_sample = AllocMem(sample_size, MEMF_CHIP);
/* Can be fast memory: */
APTR data_buffer = AllocMem(buf_size, MEMF_ANY);
APTR code = AllocMem(code_size, MEMF_ANY);
Why? The custom chips (Agnus, Alice) can only DMA from chip RAM. A bitmap in fast RAM will display garbled, and an audio sample in fast RAM won't play.
The book provides a debug-check pattern: at startup, scan all your AllocMem calls and verify that anything DMA-related is MEMF_CHIP. The modern equivalent is the _chk_abort + MEMF_KICKBACK mechanism in OS 4.x.
Static vs dynamic allocation (page 392)¶
The book contrasts the two patterns:
Static (.data / .bss)¶
struct Window *my_window; /* ← this is a 4-byte pointer, in BSS */
struct BitMap my_bitmap; /* ← in BSS if it's a global */
The compiler puts globals in .bss (uninitialised) or .data (initialised) sections. The OS loads these as part of the program binary. Their lifetime is the program's lifetime.
Dynamic (AllocMem)¶
struct Window *my_window = (struct Window *)AllocMem(sizeof(struct Window), MEMF_CLEAR);
For data that's: - too large for the stack - has variable size - has different lifetime than the program
Stack¶
void f(void) {
int local[100]; /* ← on the stack; freed when f returns */
}
Stack allocation is fast but limited (default 4 KB; configurable per-task).
The book explicitly warns: on the Amiga, large stack allocations are dangerous. The default stack is small (4 KB), and stack overflow causes silent corruption. Use
AllocMemfor anything over a few hundred bytes.
Memory alignment (page 395)¶
Some Amiga hardware requires aligned access: - chip DMA for bitmaps and audio: must be 16-bit aligned minimum (worst case) - MC68020+ with caches: must be aligned to cache line (16 bytes) for performance - copper lists: must be on a long-word boundary
AllocMem returns memory aligned to at least the largest atomic unit (a long word, 4 bytes). For higher alignment, allocate more than you need and align manually:
APTR raw = AllocMem(size + 16, MEMF_ANY | MEMF_CLEAR);
APTR aligned = (APTR)(((ULONG)raw + 15) & ~15);
V39+ alternative:
AllocVecAligned(size, requirements, alignment)does this for you.
What the book does NOT cover (modern extensions)¶
- AVL trees for memory tracking (V50+, AmigaOS 4.x)
- Slab allocators for high-throughput subsystems (used internally by OS 4)
- Pinned memory for OS 4 / MorphOS / AROS
- Lock-free atomic allocation for multi-threaded code (OS 4.x pthreads)
- Memory protection (
MEMF_PROTECT) — V50+; OS 4 supports per-page protections
See exec/memory-pools.md for the modern pool API, and library-reference/exec-library.md for the full function-by-function reference.
Memory debugging (page 396)¶
The book closes with a debugging checklist:
- For every
AllocMem, ensure a matchingFreeMemwith the correct size. - For every
AllocVec, ensure a matchingFreeVec. - Watch for memory leaks — long-running tasks should periodically call
AvailMem(MEMF_ANY)and log. - Use
MEMF_CLEARunless you have a reason not to. - Avoid
MEMF_CHIPunless the data is DMA-targetted.
Modern tools that extend this:
- MungWall — adds guard bands around each allocation, catches overruns. See debugging/amiga-debugging-tools.md.
- Enforcer — hits hits for illegal memory accesses. Same page.
- MemSniff — tracks every allocation/deallocation, shows leaks.
Common pitfalls (modern callouts)¶
The book's section 3.11 closes with a list of bugs it has seen. The list is timeless:
- AllocMem success but pointer was NULL — check return value, always
- AllocMem wrong size flag —
MEMF_CHIPfor code, orMEMF_FASTfor a bitmap - Free with wrong size — memory corruption, hard to debug
- Free twice — same
- Free in interrupt — many allocators don't support that
- Alloc on task A, free on task B — also not always supported
For modern context: OS 4.x memory protection makes some of these into crashes instead of silent corruption, which is an improvement.
Sources¶
- Bleek, Jennrich, Schulz, Amiga C for Advanced Programmers, Abacus / Data Becker, ~1991, Chapter 3 section 3.11 (pages ~270–280 of the printed book; PDF pages 379–end of the scanned source).
- Full OCR text:
raw/c-programming/amiga-c-for-advanced-programmers.md. - Cross-references:
exec/memory-pools.md,exec/exec-library-reference.md,library-reference/exec-library.md,conventions/coding-standards.md,debugging/amiga-debugging-tools.md.