cgccassemblyx86-64word-size

Size of local variable in assembly


I have following C function:

void function(int a) {
  char buffer[1];
}

It produces following assembly code(gcc with 0 optimization, 64 bit machine):

function:
  pushq %rbp
  movq  %rsp, %rbp
  movl  %edi, -20(%rbp)
  nop
  popq  %rbp
  ret

Questions:

Thanks in advance and sorry if question is duplicated, I was not able to find the answer.


Solution

  • movl %edi, -20(%rbp) is spilling the function arg from a register into the red-zone below the stack pointer. It's 4 bytes long, leaving 16 bytes of space above it below RSP.

    gcc's -O0 (naive anti-optimized) code-gen for you function doesn't actually touch the memory it reserved for buffer[], so you don't know where it is.

    You can't infer that buffer[] is using up all 16 bytes above a in the red zone, just that gcc did a bad job of packing locals efficiently (because you compiled with -O0 so it didn't even try). But it's definitely not 20 because there isn't that much space left. Unless it put buffer[] below a, somewhere else in the rest of the 128-byte red-zone. (Hint: it didn't.)


    If we add an initializer for the array, we can see where it actually stores the byte.

    void function(int a) {
      volatile char buffer[1] = {'x'};
    }
    

    compiled by gcc8.2 -xc -O0 -fverbose-asm -Wall on the Godbolt compiler explorer:

    function:
        pushq   %rbp
        movq    %rsp, %rbp               # function prologue, creating a traditional stack frame
    
        movl    %edi, -20(%rbp) # a, a
    
        movb    $120, -1(%rbp)  #, buffer
    
        nop                             # totally useless, IDK what this is for
        popq    %rbp                    # tear down the stack frame
        ret     
    

    So buffer[] is in fact one byte long, right below the saved RBP value.

    The x86-64 System V ABI requires 16-byte alignment for automatic storage arrays that are at least 16 bytes long, but that's not the case here so that rule doesn't apply.

    I don't know why gcc leaves extra padding before the spilled register arg; gcc often has that kind of missed optimization. It's not giving a any special alignment.

    If you add extra local arrays, they will fill up that 16 bytes above the spilled arg, still spilling it to -20(%rbp). (See function2 in the Godbolt link)

    I also included clang -O0, and icc -O3 and MSVC optimized output, in the Godbolt link. Fun fact: ICC chooses to optimize away volatile char buffer[1] = {'x'}; without actually storing to memory, but MSVC allocates it in the shadow space. (Windows x64 uses a different calling convention, and has 32B shadow space above the return address instead of a 128B red zone below the stack pointer.)

    clang/LLVM -O0 chooses to spill a right below RSP, and put the array 1 byte below that.


    With just char buffer instead of char buffer[1]

    We get movl %edi, -4(%rbp) # a, a from gcc -O0. It apparently optimizes away the unused and uninitialized local variable entirely, and spills a right below the saved RBP. (I didn't run it under GDB or look at the debug info to see if &buffer would give us.)

    So again, you're mixing up a with buffer.

    If we initialize it with char buffer = 'x', we're back to the old stack layout, with buffer at -1(%rbp).

    Or even if we just make it volatile char buffer; without an initializer, then space for it exists on the stack and a is spilled to -20(%rbp) even with no store done to buffer.