Размер локальной переменной в сборке

У меня есть следующая функция 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

Другие вопросы по тегам