Выделение стека, заполнение и выравнивание

Я пытался глубже понять, как компиляторы генерируют машинный код, и, в частности, как GCC работает со стеком. Поступая так, я писал простые программы на C, компилировал их в сборку и изо всех сил пытался понять результат. Вот простая программа и результат, который она генерирует:

asmtest.c:

void main() {
    char buffer[5];
}

asmtest.s:

pushl   %ebp
movl    %esp, %ebp
subl    $24, %esp
leave
ret

Что меня озадачивает, так это то, почему для стека выделяется 24 байта. Я знаю, что из-за того, как процессор обращается к памяти, стек должен выделяться с шагом 4, но если бы это было так, мы должны переместить указатель стека только на 8 байтов, а не на 24. Для справки, буфер 17 bytes создает указатель стека, перемещаемый на 40 байтов, и никакой буфер вообще не перемещает указатель стека 8. Буфер между 1 и 16 байтами включительно перемещает ESP 24 байта.

Теперь, предполагая, что 8 байтов - необходимая константа (для чего она нужна?), Это означает, что мы выделяем блоки по 16 байтов. Почему компилятор выравнивается таким образом? Я использую процессор x86_64, но даже для 64-битного слова требуется выравнивание только на 8 байт. Почему неточность?

Для справки я компилирую это на Mac, работающем под управлением 10.5 с gcc 4.0.1 и без включенной оптимизации.


person David    schedule 30.06.2009    source источник
comment
Связанный: Почему System V / AMD64 ABI требует выравнивания стека 16 байт?, рассуждения применимы также к i386 SysV ABI и gcc -mprefered-stack-boundary настройка по умолчанию, которая составляла 16 байтов для 32-битного кода еще до того, как i386 SysV ABI официально изменился на требование / гарантию.   -  person Peter Cordes    schedule 11.04.2018
comment
Странно, я пробовал тот же код с -mpreferred-stack-boundary=4, но есть только вычитание 16 из esp.   -  person Ta Thanh Dinh    schedule 13.06.2018
comment
Связано: Почему GCC выделяет в стеке больше места, чем необходимо, сверх того, что необходимо для выравнивания? - sub $8, %esp следует повторно выровнять стек и сделайте эти 8 байтов пригодными для использования в массиве. Дополнительные 16 - это пропущенная оптимизация gcc.   -  person Peter Cordes    schedule 25.07.2020


Ответы (6)


Это функция gcc, управляемая -mpreferred-stack-boundary=n, когда компилятор пытается сохранить элементы в стеке, выровненные по 2^n. Если вы измените n на 2, в стеке будет выделено только 8 байтов. Значение по умолчанию для n - 4, т.е. он будет пытаться выровняться по 16-байтовым границам.

Почему по умолчанию 8 байтов, а затем 24 = 8 + 16 байтов, потому что стек уже содержит 8 байтов для leave и ret, поэтому скомпилированный код должен сначала скорректировать стек на 8 байтов, чтобы выровнять его до 2 ^ 4 = 16.

person laalto    schedule 30.06.2009
comment
неужели push% ebp сделал esp уменьшился на 8 байт? плюс 8 байтов ret, там уже должно быть выровнено с 16 байтами. Зачем компилятору дозы эти дополнительные 8 байтов? - person Joe.Z; 12.07.2013
comment
О, я понял. Это 32-битная машина. Извините. Это должно быть ret 4 байта + EBP 4 байта + выровненный 8 байт + буфер 16 - person Joe.Z; 12.07.2013
comment
Текущие версии i386 и x86-64 System V ABI требуют выравнивания стека 16 Б (перед инструкцией call), поэтому функциям разрешено это допускать. Раньше для i386 ABI требовалось выравнивание только 4B. (ссылки на документы ABI см. в stackoverflow.com/tags/x86/info). GCC также сохраняет %esp выровненным даже в листовых функциях (которые не вызывают другие функции), когда ему нужно зарезервировать какое-либо пространство, и вот что здесь происходит. - person Peter Cordes; 07.09.2017

Семейство инструкций SSEx ТРЕБУЕТ, чтобы упакованные 128-битные векторы были выровнены по 16 байтам - в противном случае вы получите segfault при попытке загрузить / сохранить их. Т.е. если вы хотите безопасно передавать 16-байтовые векторы для использования с SSE в стеке, стек должен быть постоянно выровнен по 16. GCC учитывает это по умолчанию.

person stormsoul    schedule 30.06.2009
comment
Возможно, у меня слишком мало опыта в этом вопросе, чтобы утверждать, что ваш ответ неверен. Но разве вы не используете movupd и подобные u выровненные инструкции именно для этой цели (загрузка / сохранение невыровненных упакованных данных)? Насколько я понимаю, вы можете получить неправильное поведение при попытке использовать movapd и аналогичные инструкции для невыровненных данных, но невыровненные данные не должны быть проблемой в целом. - person andreee; 15.12.2015
comment
@andreee: movups медленнее на Core2 и более ранних версиях, даже если данные выровнены. ABI был разработан еще тогда, когда все процессоры были такими. Кроме того, выравнивание позволяет paddd xmm0, [rsp] вместо отдельной movdqu инструкции. См. Почему System V / AMD64 ABI предписать выравнивание стека по 16 байт? - person Peter Cordes; 11.04.2018

Я нашел этот сайт, на котором внизу страницы есть достойное объяснение того, почему стек может быть больше. Масштабируйте концепцию до 64-битной машины, и она может объяснить то, что вы видите.

person Chris Arguin    schedule 30.06.2009

LWN есть статья о выравнивании памяти, которая может вас заинтересовать.

person J-16 SDiZ    schedule 30.06.2009

Для Mac OS X / Darwin x86 ABI требуется выравнивание стека в 16 байт. Это не относится к другим платформам x86, таким как Linux, Win32, FreeBSD ...

person Ringding    schedule 06.08.2009
comment
Фактическое требование ABI состоит в том, чтобы стек был выровнен по 16 байтам на границах вызовов функций. - person Stephen Canon; 24.11.2009
comment
Это правда, но поскольку прологи / эпилоги функций - это почти единственные места, где изменяется указатель стека, это почти то же самое, что сказать, что он должен быть выровнен всегда. - person Ringding; 30.11.2009

Эти 8 байтов присутствуют, потому что первая инструкция помещает в стек начальное значение% ebp (при условии, что оно 64-битное).

person brian sharon    schedule 30.06.2009
comment
И адрес возврата, и базовый указатель помещаются в стек. - person dreamlax; 30.06.2009