The Heap and Dynamic Memory
Unlike stack memory, which is automatically reclaimed when a function returns, and static memory declared in .data/.bss, which exists for the program's entire lifetime, heap memory is requested and released explicitly at runtime, giving the program control over allocation sizes and lifetimes that aren't known until the program is actually running. On Linux, the two lowest-level mechanisms to grow the heap are the brk/sbrk syscalls, which extend a single contiguous program break, and mmap, which asks the kernel for an entirely separate mapped region, typically used for larger allocations.
Cricket analogy: A team management calling up a player from the reserves only when an injury happens mid-match, rather than fielding a fixed squad decided before the toss, mirrors heap allocation happening on demand at runtime rather than being fixed in advance.
brk/sbrk and mmap
The brk syscall sets the program break — the end of the process's data segment — to an absolute address, while sbrk (a libc wrapper, not a syscall itself) adjusts it by a relative increment and returns the previous break address, which is exactly where the newly available memory begins. Because brk only ever moves one boundary up or down, it is inherently a single contiguous region and cannot easily return memory to the OS except by shrinking from the very top, which is why general-purpose allocators built on brk must manage free lists internally rather than repeatedly calling brk for every small allocation.
Cricket analogy: Extending a single boundary rope marking the edge of a training ground, rather than pitching separate tents elsewhere, mirrors brk's approach of growing one contiguous region rather than scattering separate allocations.
; minimal heap allocation using the brk syscall (Linux x86-64)
section .text
global _start
_start:
; find current break: brk(0)
xor rdi, rdi
mov eax, 12 ; sys_brk
syscall
mov r12, rax ; r12 = current break (start of new memory)
; grow the heap by 4096 bytes
lea rdi, [r12 + 4096]
mov eax, 12 ; sys_brk
syscall ; rax = new break on success
; r12 now points to 4096 usable bytes
mov dword [r12], 0x12345678 ; use the freshly allocated memory
mov eax, [r12]
mov eax, 60
xor edi, edi
syscall
mmap with MAP_ANONYMOUS|MAP_PRIVATE is generally preferred over brk for allocations above a few hundred KB, because mmap'd regions can be independently unmapped with munmap and returned to the OS immediately, whereas brk-based heap space can typically only shrink from the very top of the single contiguous region.
Manual Allocator Bookkeeping and Leaks
A minimal heap allocator built on top of brk needs its own bookkeeping: each allocated block is typically prefixed with a small header storing at least its size (and often a free/used flag and pointers to neighboring blocks), so that a corresponding 'free' operation knows how many bytes to reclaim and can potentially merge adjacent free blocks to fight fragmentation. Since assembly has no automatic garbage collector, forgetting to free a block — or losing the only pointer to it by overwriting the register/variable holding its address — permanently leaks that memory for the remainder of the process's life.
Cricket analogy: A scorer's ledger noting exactly how many overs each substitute fielder is registered for, so they can be correctly released back to the reserve bench afterward, mirrors an allocator header tracking a block's size for later freeing.
Losing the only pointer to a heap block — by overwriting the register or stack slot holding its address before calling free/munmap — makes that memory permanently unreachable for the rest of the process's life. Since assembly has no garbage collector, long-running programs with this bug slowly exhaust available memory, a classic and often hard-to-diagnose memory leak.
- Heap memory is requested and released explicitly at runtime, unlike automatically-managed stack memory or fixed-lifetime static memory.
- brk sets the program break to an absolute address; sbrk (a libc convenience wrapper) adjusts it by a relative amount and returns the old break.
- brk manages one single contiguous region, so it generally can only shrink from the top, making it awkward for returning arbitrary freed memory to the OS.
- mmap with MAP_ANONYMOUS creates independent mapped regions well suited to large allocations, since each can be unmapped separately with munmap.
- A hand-built allocator typically stores a header (at least a size field) before each block so a later free operation knows how much memory to reclaim.
- Assembly has no garbage collector; every allocation must be explicitly freed, and losing the only pointer to a block leaks that memory permanently.
Practice what you learned
1. What is the fundamental difference between heap memory and stack memory?
2. What does the sbrk syscall wrapper return on success?
3. Why is mmap often preferred over brk for large allocations?
4. What is the purpose of the header commonly placed before each block in a hand-rolled allocator?
5. What happens if you overwrite the only register/variable holding a heap block's address before freeing it?
Was this page helpful?
You May Also Like
Pointers and Indirection
How addresses, dereferencing, pointer arithmetic, LEA, and multiple levels of indirection work at the machine level in x86-64 assembly.
Structures in Assembly
How fixed-layout structures are declared with NASM's struc directive, addressed by named offset, nested, and arrayed in x86-64 assembly.
Arrays in Assembly
How fixed-size, contiguous arrays are declared, laid out in memory, and indexed using scaled addressing in x86-64 assembly.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics