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:
char buffer
instead of char buffer[1]
the offset is 4 bytes, but I expected to see 8, since machine is 64 bit and I thought it will use qword(64 bit).Thanks in advance and sorry if question is duplicated, I was not able to find the answer.
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.
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
.