Почему компилятор генерирует этот код?

Я разобрал объектный файл (скорее всего, сгенерированный с использованием компилятора Visual С++) с помощью DumpBin и увидел следующий фрагмент кода:

...         ...
mov         dword ptr [ebp-4],eax       // Why save EAX?
push        dword ptr [ebp+14h]
push        dword ptr [ebp+10h]
push        dword ptr [ebp+0Ch]
push        dword ptr [ebp+8]
mov         eax,dword ptr [ebp-4]       // Why restore EAX? Did it change at all?
call        <function>
...         ...

Может кто-нибудь объяснить, почему регистр EAX сохраняется и восстанавливается в этих четырех инструкциях push?

Ответ 1

Кроме того, возможно, он скомпилирован в режиме выпуска, но эта переменная была отмечена как volatile, которая сообщает компилятору, что такая переменная может измениться без его знания, поэтому она вынуждена постоянно писать/восстанавливать ее на/из стек

Ответ 2

Было ли это построено в режиме отладки? Если это так, компилятор хранит каждую локальную переменную в стеке, чтобы отладчик мог найти их согласованным образом.

Эквивалент таких ненужных хранилищ и перезагрузок является одной из оптимизаций, которые представляют собой режим "выпуска".

Ответ 3

volatile или нет, единственная техническая причина, по которой EAX должна быть инициализирована непосредственно перед вызовом функции в Windows, была, если объявлен function __syscall, то есть с использованием соглашения о вызове Windows CS_SYSCALL. Понятно, что это немного похоже на соглашение UN * X x86_64, где %al содержит количество аргументов типа с плавающей точкой, переданных в регистры %xmm.

Соглашение о вызове syscall для Windows идентично __cdecl, т.е. функция args на стеке в обратном порядке, но с добавлением, что AL содержит счетчик количества аргументов; это делается так, что код ядра, который обычно находится на последнем конце, знает, сколько данных нужно считывать из стека пользователя в стек ядра для извлечения аргументов.

EAX - это регистр царапин для всех соглашений о вызовах на 32-битной Windows, его значение никогда не сохраняется над вызовами функций, инициализируя его непосредственно перед выполнением вызова, является избыточным. Даже если переменная, которую она удерживает, была volatile - потому что простая перегрузка не является барьером памяти и не "фиксирует" предыдущий магазин. Кроме того, местоположение [EBP - 4] находится внутри стека, поэтому переменная локальна (а volatile - не имеет смысла).

Если это не пропущенная оптимизация, то это может быть вызов __syscall function(...) с различным количеством аргументов, например, гипотетически

__syscall printf_syscall_conv(char *fmt, ...);

void possibly_print_three_vals(char *fmt, int val1, int val2, int val3)
{
    if (*strchr('%', fmt) == '\0')    // if no "%" in fmt, pass no args
        printf_syscall_conv(fmt);
    else
        printf_syscall_conv(fmt, val1, val2, val3);
}

Это может привести к созданию сборки, как ваш.