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

Estoy tratando de recoger un poco de x86. Estoy comstackndo 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 comstackdor 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  void test(void) { puts("foo"); } 

cuando se comstack 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 , 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 comstackdor no ve la statement 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 comstackdor 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  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  void function(void) { puts("foo"); } void test(void) { function(); } 

entonces el comstackdor 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