Почему компилятор генерирует этот код?
Я разобрал объектный файл (скорее всего, сгенерированный с использованием компилятора 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);
}
Это может привести к созданию сборки, как ваш.