Почему gcc использует movl вместо push для передачи аргументов функции?
обратите внимание на этот код:
#include <stdio.h>
void a(int a, int b, int c)
{
char buffer1[5];
char buffer2[10];
}
int main()
{
a(1,2,3);
}
после этого:
gcc -S a.c
эта команда показывает наш исходный код в сборке.
теперь мы можем видеть в основной функции, мы никогда не используем команду "push", чтобы проталкивать аргументы
функция a в стек. и вместо этого он использовал "movel" вместо
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $3, 8(%esp)
movl $2, 4(%esp)
movl $1, (%esp)
call a
leave
Почему это происходит?
какая разница между ними?
Ответы
Ответ 1
Вот что руководство gcc должно сказать об этом:
-mpush-args
-mno-push-args
Use PUSH operations to store outgoing parameters. This method is shorter and usually
equally fast as method using SUB/MOV operations and is enabled by default.
In some cases disabling it may improve performance because of improved scheduling
and reduced dependencies.
-maccumulate-outgoing-args
If enabled, the maximum amount of space required for outgoing arguments will be
computed in the function prologue. This is faster on most modern CPUs because of
reduced dependencies, improved scheduling and reduced stack usage when preferred
stack boundary is not equal to 2. The drawback is a notable increase in code size.
This switch implies -mno-push-args.
По-видимому, -maccumulate-outgoing-args
включен по умолчанию, переопределяя -mpush-args
. Явная компиляция с -mno-accumulate-outgoing-args
возвращается к методу PUSH
, здесь.
Обновление 2019 года: современные процессоры имеют эффективную технологию push/pop по сравнению с Pentium M.
-mno-accumulate-outgoing-args
(и использование push) в конце концов стали значением по умолчанию для -mtune=generic
в январе 2014 года.
Ответ 2
Этот код просто непосредственно помещает константы (1, 2, 3) в позиции смещения из (обновленного) указателя стека (esp). Компилятор выбирает "push" вручную с тем же результатом.
"push" обе устанавливают данные и обновляют указатель стека. В этом случае компилятор сокращает это только до одного обновления указателя стека (по сравнению с тремя). Интересный эксперимент состоял бы в том, чтобы попытаться изменить функцию "а", чтобы принять только один аргумент, и посмотреть, изменяется ли шаблон инструкции.
Ответ 3
gcc выполняет всевозможные оптимизации, включая выбор инструкций, основанных на скорости выполнения конкретного процессора, для которого оптимизирован. Вы заметите, что такие вещи, как x *= n
, часто заменяются сочетанием SHL, ADD и/или SUB, особенно когда n является константой; в то время как MUL используется только тогда, когда средняя продолжительность выполнения (и кеш /etc. footprints) комбинации SHL-ADD-SUB будет превышать значение MUL, или n
не является константой (и, таким образом, использует циклы с shl-add- sub будет дороже).
В случае аргументов функции: MOV может быть распараллелирован аппаратным обеспечением, в то время как PUSH не может. (Второй PUSH должен дождаться завершения первого PUSH из-за обновления регистра esp.) В случае аргументов функции MOV могут запускаться параллельно.
Ответ 4
Возможно ли это на OS X? Я где-то читал, что требуется, чтобы указатель стека был выровнен по 16-байтовым границам. Возможно, это объяснит такое генерирование кода.
Я нашел статью: http://blogs.embarcadero.com/eboling/2009/05/20/5607
Ответ 5
Набор инструкций Pentium не имеет инструкции для нажатия константы в стек. Поэтому использование push
было бы медленным: программе пришлось бы поместить константу в регистр и нажать регистр:
...
movl $1, %eax
pushl %eax
...
Итак, компилятор обнаруживает, что использование movl
выполняется быстрее.
Я думаю, вы можете попробовать назвать свою функцию переменной вместо константы:
int x;
scanf("%d", &x); // make sure x is not a constant
a(x, x, x);