A Glimpse of the Heap
What new and delete look like in assembly, and how they differ from stack allocation.
A Third Place for Data
So far variables have lived on the stack (locals, reached through rbp) or in fixed sections of the program image (globals, reached RIP-relative). There is a third home: the heap, the pool of memory you ask for explicitly with new and give back with delete. The heap is special because the program decides at run time how much it needs and for how long, rather than the compiler deciding the layout in advance.
In assembly, that "ask at run time" shows up as something we have mostly avoided until now: function calls. Stack allocation is free in the sense that it costs no call; it is just an adjustment of rsp. Heap allocation is a request to the runtime library, so new and delete become call instructions. Recognising those calls is the whole goal of this lesson.
new Is a Call to operator new
Here is a function that allocates one int on the heap and stores a value in it:
int* allocate(int value)
{
return new int{value};
}
At -O2:
allocate(int):
push rbx
mov ebx, edi
mov edi, 4
call operator new(unsigned long)@PLT
mov DWORD PTR [rax], ebx
pop rbx
ret
Read it slowly. The expression new int{value} does two distinct things, and both are visible:
-
Allocate raw memory.
mov edi, 4puts the number4into the first argument register, andcall operator new(unsigned long)requests that many bytes from the heap. The4issizeof(int):newalways asks the allocator for the size of the thing being created.operator newreturns the address of the new block inrax, the usual return register. (The@PLTsuffix just means the call goes through the linker's procedure linkage table to reach the library function; you can read past it.) -
Initialise the memory.
mov DWORD PTR [rax], ebxstoresvalue(saved inebxacross the call) into the freshly allocated block at[rax]. This is an ordinary pointer store, exactly like Lesson 1:[rax]is the heap address, and we write 4 bytes into it.
So new int{value} is "call operator new for 4 bytes, then store the initial value into the returned address." The address in rax is also the function's return value, since allocate hands the pointer back. The push rbx/pop rbx around it is just preserving ebx so the value survives the call, a bookkeeping detail from Chapter 4.
delete Is a Call to operator delete
Freeing is the mirror image:
void release(int* p)
{
delete p;
}
release(int*):
test rdi, rdi
je .L4
mov esi, 4
jmp operator delete(void*, unsigned long)@PLT
.L4:
ret
The pointer p arrives in rdi. Before freeing, test rdi, rdi / je .L4 checks whether the pointer is null: delete on a null pointer is defined to do nothing, so if rdi is zero the code jumps straight to the ret and returns without freeing. Otherwise it sets up the call to operator delete, passing the pointer (already in rdi) and the size 4 (in esi), and hands control to the deallocator. (Here the compiler used jmp rather than call as a tail-call optimisation: since freeing is the last thing the function does, it jumps into operator delete and lets that function's ret return all the way back to our caller. Read the jmp here as "call and return.")
The pattern to memorise is simple: new becomes a call to operator new, and delete becomes a call to operator delete. Whenever you spot those two symbols in a listing, you are looking at heap activity.
Stack Allocation Has No Such Call
Contrast all of that with putting the same int on the stack:
int useStack(int value)
{
int local{value};
return local + 1;
}
At -O0:
useStack(int):
push rbp
mov rbp, rsp
mov DWORD PTR -20[rbp], edi
mov eax, DWORD PTR -20[rbp]
mov DWORD PTR -4[rbp], eax
add eax, 1
pop rbp
ret
There is no call anywhere. The local int simply occupies a slot at -4[rbp] inside the function's existing stack frame. No allocator is consulted, and there is nothing to free: when the function returns and the frame is torn down, the slot is gone automatically. This is why stack allocation is so cheap and why local variables need no delete. The heap trades that automatic, free cleanup for the flexibility of run-time-sized, run-time-lifetime memory, and it pays for it with those operator new and operator delete calls.
Why This Matters for Reading Listings
When you scan an optimized listing and see calls to operator new and operator delete, you immediately know three things: the code touches the heap, there is a matching allocate/free pair to look for, and the size argument tells you how big each allocation is. If those calls are unbalanced, an allocation with no corresponding free, that is the assembly-level shape of a memory leak. You will not chase leaks by reading assembly in practice, but it is striking that the bug is literally visible: a call operator new with no call operator delete to match it.
This is also why modern C++ steers you toward containers like std::vector and smart pointers like std::unique_ptr: they still call operator new and operator delete underneath, but they emit the delete for you automatically in their destructors, so the pairing is guaranteed. Under the hood it is the same two calls you just learned to recognise.
Key Takeaways
- The heap is a third place for data, alongside the stack and the fixed global sections; it is requested at run time.
newcompiles to a call tooperator new, passing the byte size (e.g.mov edi, 4for anint); the returned address comes back inrax.- Constructing the object is a separate step: an ordinary store into the returned address, like
mov DWORD PTR [rax], ebx. deletecompiles to a call tooperator delete, often guarded by a null-pointer check first.- Stack allocation involves no call at all; a local just occupies a slot in the existing frame and is reclaimed when the frame is torn down.
- Spotting
operator new/operator deletein a listing means heap activity; an unmatchedoperator newis what a memory leak looks like in assembly.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
A Glimpse of the Heap - Quiz
Test your understanding of the lesson.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!