Распределение стека, отступы и выравнивание
Я пытаюсь получить более глубокое понимание того, как компиляторы генерируют машинный код, а точнее, как GCC имеет дело со стеком. При этом я пишу простые программы на С, компилируя их в сборку и изо всех сил пытаюсь понять результат. Здесь простую программу и выходные данные, которые она генерирует:
asmtest.c
:
void main() {
char buffer[5];
}
asmtest.s
:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
leave
ret
Что меня озадачивает, так почему для стека выделяется 24 байта. Я знаю, что из-за того, как процессор обращается к памяти, стек должен быть распределен с шагом 4, но если это так, мы должны только перемещать указатель стека на 8 байтов, а не 24. Для справки буфер 17 байт вырабатывает указатель стека, перемещаемый 40 байтами, и никакой буфер вообще не перемещает указатель стека 8. Буфер между 1 и 16 байтами включительно перемещает ESP
24 байта.
Теперь, полагая, что 8 байтов - необходимая константа (для чего это необходимо?), это означает, что мы выделяем куски по 16 байт. Почему компилятор должен быть таким образом совмещен? Я использую процессор x86_64, но даже для 64-битного слова требуется только выравнивание по 8 байт. Почему расхождение?
Для справки я компилирую это на Mac, работающем 10.5 с gcc 4.0.1, и оптимизация не включена.
Ответы
Ответ 1
Это функция gcc, управляемая -mpreferred-stack-boundary=n
, когда компилятор пытается сохранить элементы в стеке, выровненные с 2^n
. Если вы изменили n
на 2
, он выделил бы только 8 байтов в стеке. Значение по умолчанию для n
равно 4
, т.е. Будет пытаться выровнять по 16-байтовым границам.
Почему там "по умолчанию" 8 байтов, а затем 24 = 8 + 16 байт, потому что стек уже содержит 8 байтов для leave
и ret
, поэтому скомпилированный код должен сначала отрегулировать стек на 8 байтов, чтобы получить он выровнен до 2 ^ 4 = 16.
Ответ 2
Семейство инструкций SSEx ТРЕБУЕТ упакованных 128-битных векторов, которые должны быть выровнены с 16 байтами - в противном случае вы получите segfault, пытающийся загрузить/сохранить их. То есть если вы хотите безопасно передавать 16-байтные векторы для использования с SSE в стеке, стек необходимо последовательно поддерживать в соответствии с 16. Учетные записи GCC по умолчанию.
Ответ 3
Я нашел этот сайт, в котором есть несколько полезных объяснений внизу страницы о том, почему стек может быть больше. Масштабируйте концепцию до 64-битной машины, и она может объяснить, что вы видите.
Ответ 4
У LWN есть статья о выравнивании памяти, которая может показаться интересной.
Ответ 5
Для Mac OS X/Darwin x86 ABI требуется выравнивание стека по 16 байт. Это не так на других платформах x86, таких как Linux, Win32, FreeBSD...
Ответ 6
8 байтов есть потому, что первая команда подталкивает начальное значение% ebp в стеке (предполагая 64-разрядную).