Поскольку main
является особенным, вы часто можете получить лучшие результаты, выполняя подобные вещи в другой функции (предпочтительно в своем собственном файле без main
). Например:
void foo(int x) {
if (x == 0) {
printf("testing\n");
}
}
, вероятно, будет гораздо более понятным, чем сборка. Это также позволит вам скомпилировать с оптимизацией и при этом соблюдать условное поведение. Если бы вы собирали исходную программу с любым уровнем оптимизации выше 0, это, вероятно, покончило бы с сравнением, поскольку компилятор мог бы продолжить и рассчитать результат этого. С этим кодом часть сравнения скрыта от компилятора (в параметре x
), поэтому компилятор не может выполнить эту оптимизацию.
Что такое дополнительный материал на самом деле
_main:
pushl %ebpz
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
Это настройка стекового фрейма для текущей функции. В x86 кадр стека - это область между значением указателя стека (SP, ESP или RSP для 16, 32 или 64 бит) и значением базового указателя (BP, EBP или RBP). Это предположительно, где локальные переменные живут, но не совсем, и явные кадры стека в большинстве случаев являются необязательными Однако использование массивов alloca
и / или переменной длины потребует их использования.
Эта конкретная конструкция фрейма стека отличается от структуры, отличной от main
, поскольку она также обеспечивает выравнивание стека на 16 байт. Вычитание из ESP увеличивает размер стека более чем достаточно для хранения локальных переменных, а andl
эффективно вычитает от 0 до 15 из него, выравнивая его на 16 байт. Это выравнивание кажется чрезмерным, за исключением того, что оно заставит стек также начинать выравнивание кэша и выравнивание слов.
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
movl %eax, -8(%ebp)
movl -8(%ebp), %eax
call __alloca
call ___main
Я не знаю, что все это делает. alloca
увеличивает размер кадра стека, изменяя значение указателя стека.
movl $0, -4(%ebp)
cmpl $0, -4(%ebp)
jne L2
movl $LC0, (%esp)
call _printf
L2:
movl $0, %eax
Я думаю, вы знаете, что это делает. Если нет, то movl
просто перед тем, как call
перемещает адрес вашей строки в верхнее расположение стека, чтобы его можно было извлечь с помощью printf. Он должен быть передан в стек, чтобы printf мог использовать свой адрес для вывода адресов других аргументов printf (если таковые имеются, которых нет в данном случае).
leave
Эта инструкция удаляет кадр стека, о котором говорилось ранее. По существу это movl %ebp, %esp
, за которым следует popl %ebp
. Существует также инструкция enter
, которую можно использовать для создания стековых фреймов, но gcc ее не использовал. Когда стековые фреймы явно не используются, EBP
может использоваться как универсальный регистр, и вместо leave
компилятор просто добавляет размер фрейма стека к указателю стека, что уменьшит размер стека на размер фрейма. ,
ret
Мне не нужно объяснять это.
Когда вы компилируете с оптимизацией
Я уверен, что вы перекомпилируете все это с разными уровнями оптимизации, поэтому я укажу на то, что может произойти, что вы, вероятно, найдете странным. Я наблюдал gcc
замену printf
и fprintf
на puts
и fputs
, соответственно, когда строка формата не содержала %
и не было передано никаких дополнительных параметров. Это связано с тем, что (по многим причинам) гораздо дешевле звонить на номера puts
и fputs
, и в итоге вы все равно получаете то, что хотели напечатать.