| rax | |
| rsp |
0000000140001004
(the mov dword ptr [rsp], 5CA1AB1Eh in func()), what does the stack look like?
This table shows every 8-byte slot — including the five rows the stepper above collapses
into "…". Fill in each value, then check yourself. (That state is Step 4 up top.
Also: the original slide lists 0014FED0 — a typo for 0014FDD0, fixed here.)
sub rsp, 18h — 24 bytes for a single 4-byte int?Two ingredients: allocation granularity + alignment.
MSVC hands out local-variable storage in 16-byte chunks, so one 4-byte int rounds up
to 0x10. Then 8 more bytes are added so that the frame keeps rsp 16-byte aligned:
the call into func pushed an 8-byte return address (rsp ended in 8), and
8 + 0x18 = 0x20 — a multiple of 16. Hence 0x18.
Compare with main, which has no locals but calls func: its sub rsp, 28h
is 32 bytes of shadow space + 8 alignment. func is a leaf (calls nobody), so it needs no
shadow space — just local storage + alignment.
i live in memory at all? Why not just mov eax, 5CA1AB1Eh?This is an unoptimized build. At -O0 / MSVC debug, every local variable gets a real
stack slot, and every read/write of it becomes a real memory access — that's what lets a
debugger show and modify i at any moment.
An optimizing compiler would collapse func to mov eax, 5CA1AB1Eh; ret —
or inline the whole thing and have main return the constant directly. No frame, no store,
no load.
dword ptr?mov [rsp], 5CA1AB1Eh alone would be ambiguous — should the assembler store
1, 2, 4, or 8 bytes at that address? The immediate doesn't carry a size, and neither does a
bare memory operand. dword ptr pins it to 4 bytes, matching int.
And that's exactly why the upper half of the stack slot stays garbage: a dword store touches exactly 4 bytes. Zero-extension is a register-destination rule only.
Hexspeak: 5CA1AB1E ≈ "SCALABLE". Same family as 0xBEEF and 0xF00D from
CallASubroutine1, or the classics 0xDEADBEEF and 0xCAFEBABE. Course tradition: if a constant
looks pronounceable, it probably is.