Почему в этой разборке стек увеличивается на 16 байт, когда у меня есть только одна 4-байтовая локальная переменная?
У меня возникают проблемы с пониманием того, почему компилятор решил сместить пространство стека так, как это было сделано с кодом, который я написал.
Я играл с компилятором компилятора Годболта, чтобы изучить соглашение о вызовах Си, когда я придумал простой код, который меня озадачил своим выбором.
Код находится по этой ссылке. Я выбрал GCC 8.2 x86-64, но ориентируюсь на процессоры x86, и это важно. Ниже приведена транскрипция кода C и сгенерированной сборки, переданная в Compiler Explorer.
// C code
int testing(char a, int b, char c) {
return 42;
}
int main() {
int x = testing('0', 0, '7');
return 0;
}
; Generated assembly
testing(char, int, char):
push ebp
mov ebp, esp
sub esp, 8
mov edx, DWORD PTR [ebp+8]
mov eax, DWORD PTR [ebp+16]
mov BYTE PTR [ebp-4], dl
mov BYTE PTR [ebp-8], al
mov eax, 42
leave
ret
main:
push ebp
mov ebp, esp
sub esp, 16
push 55
push 0
push 48
call testing(char, int, char)
add esp, 12
mov DWORD PTR [ebp-4], eax
mov eax, 0
leave
ret
Теперь, глядя на столбец сборки, как я понял, строка 15 отвечает за резервирование места в стеке для локальных переменных. Проблема в том, что у меня только один местный int
и смещение было на 16 байтов вместо 4. Это похоже на потерянное пространство.
Это как-то связано с выравниванием слов? Но даже если это так, если размеры регистров общего назначения составляют 4 байта, разве это выравнивание не должно быть в отношении 4 байтов?
Еще одна странная вещь, которую я вижу в отношении размещения местного char
с testing
функция. Кажется, что они занимают по 4 байта в стеке, как показано в строках 7-8, но манипулируют только младшими байтами. Почему бы не использовать только 1 байт каждый?
Эти выборы, вероятно, хорошо продуманы, и мне бы очень хотелось понять их цель (или нет цели). Или, может быть, я просто растерялся и не совсем понял.
2 ответа
Итак, по комментариям я мог бы понять, что проблема роста стека связана с требованиями i386 SystemV ABI, как заявлено @PeterCordes.
Причина, по которой символы выровнены по словам, может быть связана с поведением GCC по умолчанию для повышения скорости, что может быть следствием комментария @Ped7g. Хотя это и не определенно, это достаточно хороший ответ для меня.
Сегодня принято приобретать пространство стека кратно этому размеру по нескольким причинам:
- строки кэша способствуют такому поведению, сохраняя все данные в кэше.
- пространство для временных файлов распределяется заранее, избегая использования команд push и pop в случае, если требуется некоторое временное хранилище вне процессора.
- отдельные команды push и pop ухудшают выполнение конвейера, требуя обновления данных перед выполнением следующей инструкции. Это разъединяет зависимости данных между последовательными инструкциями и позволяет им работать быстрее.
По этой причине фактические компиляторы указывают ABI, которые должны быть разработаны таким образом.