movl %edi, -20(%rbp)
проливает функцию arg из регистра в красную зону под указателем стека.Его длина составляет 4 байта, оставляя 16 байтов над ним ниже RSP.
Код gcc -O0
(наивный антиоптимизированный) для вашей функции фактически не затрагивает память, зарезервированную для buffer[]
, так что вы не знаете, где это.
Вы не можете сделать вывод, что buffer[]
использует все 16 байтов выше a
в красной зоне, просто gcc плохо упаковалместные жители эффективно (потому что вы скомпилировали с -O0
, поэтому он даже не пытался).Но это определенно не 20, потому что осталось не так много места.Если только он не поставит buffer[]
ниже a
, где-нибудь еще в остальной части 128-байтовой красной зоны.(Подсказка: это не так.)
Если мы добавим инициализатор для массива, мы увидим, где он на самом деле хранит байт.
void function(int a) {
volatile char buffer[1] = {'x'};
}
скомпилированопо gcc8.2 -xc -O0 -fverbose-asm -Wall
в проводнике компилятора Godbolt :
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
Так что buffer[]
на самом деле длиной в один байт, вправо *На 1034 * ниже сохраненного значения RBP.
Системе ABI x86-64 System V требуется 16-байтовое выравнивание для автоматических массивов хранения длиной не менее 16 байт, но здесь это не так, поэтому это правилоне применяется.
Я не знаю, почему gcc оставляет дополнительное заполнение перед аргументом разлитого регистра arg;У gcc часто такая пропущенная оптимизация.Это не дает a
какого-либо особого выравнивания.
Если вы добавите дополнительные локальные массивы, они заполнят это 16 байтов выше пролитого аргумента, все еще разливая его до -20(%rbp)
.(См. function2
в ссылке Godbolt)
Я также включил clang -O0
и icc -O3
и оптимизированный вывод MSVC в ссылку Godbolt.Интересный факт: ICC решает оптимизировать volatile char buffer[1] = {'x'};
без фактического сохранения в памяти, но MSVC выделяет его в теневом пространстве.(Windows x64 использует другое соглашение о вызовах и имеет теневое пространство 32B над адресом возврата вместо красной зоны 128B под указателем стека.)
clang / LLVM -O0
выбирает разлив a
прямо нижеRSP, и поместите массив на 1 байт ниже этого.
С просто char buffer
вместо char buffer[1]
Мы получаем movl %edi, -4(%rbp) # a, a
от gcc -O0
.По-видимому, он полностью оптимизирует неиспользованную и неинициализированную локальную переменную и проливает a
прямо под сохраненным RBP.(Я не запускал его под GDB и не смотрел информацию об отладке, чтобы узнать, даст ли нам &buffer
.)
Итак, снова вы смешиваете a
с buffer
.
Если мы инициализируем его с char buffer = 'x'
, мы вернемся к старому стековому макету с buffer
на -1(%rbp)
.
Или даже если мы просто сделаемэто volatile char buffer;
без инициализатора, тогда место для него существует в стеке, и a
заносится в -20(%rbp)
даже без сохранения в buffer
.