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