macos gcc assembly x86-64 cpu-registers

macos - ¿Por qué% eax se pone a cero antes de una llamada a printf?



gcc assembly (2)

Estoy tratando de recoger un poco de x86. Estoy compilando en un mac de 64 bits con gcc -S-O0.

Código en C:

printf("%d", 1);

Salida:

movl $1, %esi leaq LC0(%rip), %rdi movl $0, %eax ; WHY? call _printf

No entiendo por qué% eax se borra a 0 antes de llamar a ''printf''. Como printf devuelve la cantidad de caracteres impresos en %eax mi mejor estimación es que se ha reducido a cero para prepararlo para printf pero habría supuesto que printf tendría que ser responsable de prepararlo. Además, en contraste, si llamo a mi propia función int testproc(int p1) , gcc no ve la necesidad de preparar %eax . Entonces me pregunto por qué gcc trata a printf y testproc diferente.


Del x86_64 System V ABI :

Register Usage %rax temporary register; with variable arguments passes information about the number of vector registers used; 1st return register ...

printf es una función con argumentos variables, y la cantidad de registros vectoriales utilizados es cero.

Tenga en cuenta que printf debe verificar solo %al , porque la persona que llama puede dejar basura en los bytes más altos de %rax . (Aún así, xor %eax,%eax es la forma más eficiente de cero %al )

Consulte este Q & A y la wiki de la etiqueta x86 para obtener más detalles, o para enlaces ABI actualizados si el enlace anterior está desactualizado.


En el ABI x86_64, si una función tiene argumentos variables, se espera que AL (que es parte de EAX ) contenga el número de registros de vectores utilizados para mantener los argumentos a esa función.

En tu ejemplo:

printf("%d", 1);

tiene un argumento entero por lo que no es necesario un registro vectorial, por lo que AL se establece en 0.

Por otro lado, si cambia su ejemplo a:

printf("%f", 1.0f);

entonces el literal de coma flotante se almacena en un registro vectorial y, correspondientemente, AL se establece en 1 :

movsd LC1(%rip), %xmm0 leaq LC0(%rip), %rdi movl $1, %eax call _printf

Como se esperaba:

printf("%f %f", 1.0f, 2.0f);

hará que el compilador establezca AL en 2 ya que hay dos argumentos de coma flotante:

movsd LC0(%rip), %xmm0 movapd %xmm0, %xmm1 movsd LC2(%rip), %xmm0 leaq LC1(%rip), %rdi movl $2, %eax call _printf

En cuanto a sus otras preguntas:

puts también está poniendo a cero %eax justo antes de la llamada, aunque solo toma un solo puntero. ¿Por qué es esto?

No debería. Por ejemplo:

#include <stdio.h> void test(void) { puts("foo"); }

cuando se compila con gcc -c -O0 -S , genera:

pushq %rbp movq %rsp, %rbp leaq LC0(%rip), %rdi call _puts leave ret

y %eax no se ha reducido a cero. Sin embargo, si elimina #include <stdio.h> , el ensamblaje resultante se pone a cero %eax justo antes de llamar a puts() :

pushq %rbp movq %rsp, %rbp leaq LC0(%rip), %rdi movl $0, %eax call _puts leave ret

La razón está relacionada con tu segunda pregunta:

Esto también ocurre antes de cualquier llamada a mi propia función void proc () (incluso con -O2 establecido), pero no se pone a cero cuando se llama a una función void proc2 (int param).

Si el compilador no ve la declaración de una función, no hace suposiciones sobre sus parámetros, y la función podría aceptar argumentos variables. Lo mismo se aplica si especifica una lista de parámetros vacía (que no debería, y está marcada como una característica obsoleta C por ISO / IEC). Dado que el compilador no tiene suficiente información sobre los parámetros de la función, cierra %eax antes de llamar a la función porque podría ser el caso de que la función se define como que tiene argumentos variables.

Por ejemplo:

#include <stdio.h> void function() { puts("foo"); } void test(void) { function(); }

donde function() tiene una lista de parámetros vacía, da como resultado:

pushq %rbp movq %rsp, %rbp movl $0, %eax call _function leave ret

Sin embargo, si sigue la práctica recomendada de especificar el void cuando la función no acepta parámetros, tales como:

#include <stdio.h> void function(void) { puts("foo"); } void test(void) { function(); }

entonces el compilador sabe que function() no acepta argumentos, en particular, no acepta argumentos variables, y por lo tanto no borra %eax antes de llamar a esa función:

pushq %rbp movq %rsp, %rbp call _function leave ret