Почему в этой разборке стек увеличивается на 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, которые должны быть разработаны таким образом.

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