Размер локальной переменной в сборке
У меня есть следующая функция C:
void function(int a) {
char buffer[1];
}
Он производит следующий код сборки (gcc с 0 оптимизацией, 64-битный компьютер):
function:
pushq %rbp
movq %rsp, %rbp
movl %edi, -20(%rbp)
nop
popq %rbp
ret
Вопросы:
- Почему буфер занимает 20 байтов?
- Если я заявляю
char buffer
вместоchar buffer[1]
смещение составляет 4 байта, но я ожидал увидеть 8, так как машина 64-битная, и я думал, что она будет использовать qword(64-битная).
Спасибо заранее и извините, если вопрос дублируется, я не смог найти ответ.
2 ответа
movl %edi, -20(%rbp)
проливает функцию arg из регистра в красную зону под указателем стека. Это 4 байта длиной, оставляя 16 байтов пространства над ним ниже RSP.
ССЗ -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[]
на самом деле длиной в один байт, прямо под сохраненным значением RBP.
Системе ABI x86-64 System V требуется 16-байтовое выравнивание для автоматических массивов хранения длиной не менее 16 байт, но здесь это не так, так как это правило не применяется.
Я не знаю, почему gcc оставляет дополнительное заполнение перед аргументом разлитого регистра; У gcc часто такая пропущенная оптимизация. Это не дает a
любое специальное выравнивание.
Если вы добавите дополнительные локальные массивы, они будут заполнять эти 16 байтов выше разлитого аргумента, все еще проливая его на -20(%rbp)
, (Увидеть function2
в Godbolt ссылку)
Я также включил clang -O0
, а также icc -O3
и оптимизированный вывод MSVC, по ссылке Godbolt. Интересный факт: ICC решает оптимизировать volatile char buffer[1] = {'x'};
без фактического сохранения в памяти, но MSVC выделяет его в теневом пространстве. (Windows x64 использует другое соглашение о вызовах и имеет теневое пространство 32B над адресом возврата вместо красной зоны 128B под указателем стека.)
лязг /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
,
4 байта выровнены символ,8 байтов подтолкнуло RBP,8 байтов a
= 20. Начать адреса a
текущий указатель стека минус 20