Размер локальной переменной в сборке - PullRequest
0 голосов
/ 24 декабря 2018

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

0 голосов
/ 24 декабря 2018

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.

0 голосов
/ 24 декабря 2018

4 байта выровнены как символ, 8 байтов помещено в бит, 8 байтов a = 20. Начальные адреса a - текущий указатель стека минус 20

...