Search code examples
pointersassemblystackx86-64

Moving the Stack Pointer in Assembly


I'm confused about why if you wanted to allocate 8-byte storage on the stack, you use the following command

subq $8,%rsp

%rsp stores an address. Why does subtracting the literal value 8 from the address stored in %rsp allocate 8 bytes? In particular, why does $x correspond to x bytes?

Edit: My question was answered in this stackoverflow response. It's in terms of bytes because that's the size of an int, which is the literal argument being passed in.

Edit 2: The above is incorrect. See Bo's comment.


Solution

  • You're right. Stack pointer is just a pointer to a memory location, i.e. it holds an integer value that is a valid address. That memory block is actually allocated as a large chunk at thread initialization. It is usually large enough to avoid stack overflows. Stack pointer points to the end of that block at the beginning and gets decremented whenever a new value is pushed (and incremented whenever popped). Pushing a value to stack just moves the given value to the memory slot RSP points at and decrements it. For instance:

    push rax 
    

    is synonymous to this (except for not affecting FLAGS):

    sub rsp, 8
    mov [rsp], rax
    

    Only subtracting from RSP (instead of doing a push) just leaves you free space in the stack that you can use for your own purposes, without also overwriting whatever old value was there, and the size can be larger than one 8-byte stack slot1. That's how local variables work actually. So:

    sub rsp, 16
    

    Moves stack pointer down 16 bytes so you have space for 16 bytes you can use for whatever you want in your own function, as any combination of sizes. To release it you either need to remember to increment rsp register accordingly, or use a frame pointer like:

    mov rbp, rsp
    sub rsp, 16
    ; and here access the values using rbp-xxx instead of rsp+xxx
    
    mov rsp, rbp
    

    So the compiler could have done push rax instead of a sub rsp, 8 but that would actually require a write to memory (because the contents of rax needed to be stored). That would be a waste. Instead, moving the pointer itself is a much cheaper operation until that memory block is really needed.

    (let me know if Intel assembly syntax I used here creates confusion, I just find it much easier to write)


    Footnote 1: A multiple of 8 bytes is a good idea to keep the stack aligned. In fact the calling convention lets callees assume that RSP was aligned by 16 before a call, so callers need to do that except when calling your own functions that don't depend on that. RSP%16==0 before a call means RSP%16==8 after a call (which pushes a return address). Counting both push and sub rsp, total movement of RSP since function entry should be an odd multiple of 8.

    Some compilers will use push rax in a function prologue to move RSP, because that's more efficient that sub rsp, 8 on some CPUs, even though it does a store we don't care about. (Why does this function push RAX to the stack as the first operation?). Freshly-stored garbage is equivalent to old garbage as far as the value in that space; if you want to be clever, push 0 or something that's actually useful as an initializer for the you're reserving, instead of a mov later. (What C/C++ compiler can use push pop instructions for creating local variables, instead of just increasing esp once?).