2010-12-26 23 views
17

prestar atención a este código:¿por qué usa el movl en lugar de push?

#include <stdio.h> 
void a(int a, int b, int c) 
{ 
    char buffer1[5]; 
    char buffer2[10]; 
} 

int main() 
{ 
    a(1,2,3); 
} 

después de eso:

gcc -S a.c 

ese comando muestra nuestro código fuente en el montaje.

ahora podemos ver en la función principal, nunca usamos el comando "push" para insertar los argumentos de la función a en la pila. y usó "movel" en lugar de eso

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 

¿por qué sucede? ¿cuál es la diferencia entre ellos?

Respuesta

16

Esto es lo que el manual de gcc tiene que decir al respecto:

-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. 

Al parecer -maccumulate-outgoing-args está activada por defecto, anulando -mpush-args. Compilando explícitamente con -mno-accumulate-outgoing-args vuelve al método PUSH, aquí.

+4

Una pregunta mucho mejor sería por qué esta opción de generación de bloat '-maccumulate-outgoing-args' no se deshabilita automáticamente por' -Os'. –

+0

@R .. ¿Sabes por qué? – Tony

+0

@Tony: obviamente, porque al decidir cuál de los muchos (~ 200) indicadores de optimización para habilitar/deshabilitar para cada opción -O específica, a veces las cosas se deslizan por las grietas. – ninjalj

8

Ese código simplemente pone las constantes (1, 2, 3) en las posiciones de desplazamiento desde el puntero de pila (actualizado) (esp). El compilador elige hacer el "push" manualmente con el mismo resultado.

"push" establece los datos y actualiza el puntero de la pila. En este caso, el compilador lo reduce a una sola actualización del puntero de la pila (frente a tres). Un experimento interesante sería intentar cambiar la función "a" para tomar solo un argumento, y ver si el patrón de instrucción cambia.

+0

¿Por qué necesitaría poner la constante en un registro primero? x86 admite la pulsación de constantes inmediatas – Necrolis

+0

@ Necrolis: suficiente. Editado Gracias. –

0

El conjunto de instrucciones Pentium no tiene instrucciones para insertar una constante en la pila. Así, utilizando push sería lenta: el programa tendría que poner la constante en un registro y empujar el registro:

... 
movl $1, %eax 
pushl %eax 
... 

por lo que el compilador detecta que el uso de movl es más rápido. supongo que se puede tratar de llamar a su función con una variable en lugar de una constante:

int x; 
scanf("%d", &x); // make sure x is not a constant 
a(x, x, x); 
+6

Empujar una constante ha sido soportado desde 80286. ¿Tal vez gcc está por defecto generando el código 8086? –

+1

Parece que mi conocimiento del conjunto de instrucciones x86 está un poco desactualizado (en 20 años) :-) – anatolyg

6

gcc hace todo tipo de optimizaciones, incluyendo la selección de instrucciones basadas en la velocidad de ejecución de la CPU particular que se está optimizado para. Notarás que cosas como x *= n a menudo se reemplazan por una mezcla de SHL, ADD y/o SUB, especialmente cuando n es una constante; mientras que MUL solo se usa cuando el tiempo de ejecución promedio (y las huellas de caché/etc.) de la combinación de SHL-ADD-SUB excedería al de MUL, o n no es una constante (y por lo tanto, usar bucles con shl-add-sub sería venir más costoso).

En caso de argumentos de funciones: MOV puede ser paralelizado por hardware, mientras que PUSH no puede. (El segundo PUSH tiene que esperar a que termine el primer PUSH debido a la actualización del registro esp). En el caso de los argumentos de función, los MOV se pueden ejecutar en paralelo.

+0

¿Alguna referencia sobre este tipo de optimizaciones? Gracias. – Tony

2

¿Es esto en OS X por casualidad? Leí en alguna parte que requiere que el puntero de la pila esté alineado en los límites de 16 bytes. Eso posiblemente podría explicar este tipo de generación de código.

me encontré el artículo: http://blogs.embarcadero.com/eboling/2009/05/20/5607

+1

Para que quede claro, OS X ABI solo requiere que el puntero de la pila esté alineado en 16 bytes en el punto de llamadas a funciones externas. –

+0

Ya veo, gracias por señalar eso. Leyendo las otras respuestas, ahora entiendo que la generación del código movl está relacionada con la programación mejorada. Sin embargo, parece que la instrucción andl solo está ahí para la alineación de la pila. –

Cuestiones relacionadas