printf: ¿cómo explicar resultado dañado
-
01-10-2019 - |
Pregunta
#include <stdio.h>
int main(void)
{
double resd = 0.000116;
long long resi = 0;
printf("%lld %f %lld %f\n", resd, resd, resi, resi);
return 0;
}
da (Linux, gcc, x64)
0 0.000116 0 0.000116 ^^^^^^^^ odd, since the memory for resi is zeroed
En realidad, compilado con g ++ da resultados aleatorios en lugar de la segunda 0.
Yo entiendo que di especificadores no válidos a printf
y que desencadena no especificado comportamiento indefinido, pero me pregunto por qué se produce esta corrupción específica, ya long long
y double
tienen el mismo tamaño.
Solución
Me conseguir los mismos resultados que lo hace en mi máquina (Mac OS X, por lo que Linux ABI AMD /). Los parámetros de punto flotante se pasan en registros XMM y los parámetros enteros en registros enteros. Cuando printf
los agarra usando va_arg
, se tira del XMM cuando se ve el formato %f
, y de los otros registros cuando ve %lld
. Aquí está el desmontaje para su programa compilado (-O0
) en mi máquina:
1 _main:
2 pushq %rbp
3 movq %rsp,%rbp
4 subq $0x20,%rsp
5 movq $0x3f1e68a0d349be90,%rax
6 move %rax,0xf8(%rbp)
7 movq $0x00000000,0xf0(%rbp)
8 movq 0xf0(%rbp),%rdx
9 movq 0xf0(%rbp),%rsi
10 movsd 0xf8(%rbp),%xmm0
11 movq 0xf8(%rbp),%rax
12 movapd %xmm0,%xmm1
13 movq %rax,0xe8(%rbp)
14 movsd 0xe8(%rbp),%xmm0
15 lea 0x0000001d(%rip),%rdi
16 movl $0x00000002,%eax
17 callq 0x100000f22 ; symbol stub for: _printf
18 movl $0x00000000,%eax
19 leave
20 ret
No se puede ver lo que está pasando - la cadena de formato se pasa en %rdi
, a continuación, se pasan los parámetros (en orden) en: %xmm0
, %xmm1
, %rsi
y %rdx
. cuando printf
los consigue, se les aparece fuera en un orden diferente (el orden especificado en la cadena de formato). Eso significa que ellos aparece: %rsi
, %xmm0
, %rdx
, %xmm1
, dando los resultados que se ven. El 2
en %eax
es indicar el número de argumentos de coma flotante pasado.
Editar:
Esta es una versión optimizada - en este caso el código más corto podría ser más fácil de entender. La explicación es la misma que la anterior, pero con un poco de ruido repetitivo menos. El valor de punto flotante se carga por el movsd
en la línea 4.
1 _main:
2 pushq %rbp
3 movq %rsp,%rbp
4 movsd 0x00000038(%rip),%xmm0
5 xorl %edx,%edx
6 xorl %esi,%esi
7 movaps %xmm0,%xmm1
8 leaq 0x00000018(%rip),%rdi
9 movb $0x02,%al
10 callq 0x100000f18 ; symbol stub for: _printf
11 xorl %eax,%eax
12 leave
13 ret
Otros consejos
Es porque bajo la x86_64
C convenciones de llamada en su plataforma, los dos primeros argumentos de coma flotante se pasan en xmm0
y xmm1
, y los primeros dos argumentos enteros se pasan en GPRS (rsi
y rdx
si estás en Linux o OS X), independientemente del orden en el que aparecen.
Usted está confundido porque usted está esperando que los parámetros se pasan en la memoria; no lo son.
- El primer número debe ser un alto valor ya que usted está pasando por un doble como un entero. Debe ser% f.
- ¿Qué pasa con el período después de la "0" en "Resi"? Que lo convertirá en un doble, por lo que está tratando de cargar un doble dentro de un número entero. Eso te dará una advertencia del compilador.
- Algunas implementaciones podría estar basada en registros, por lo que ya que estás arruinando los tipos de argumentos, se confunde.
¿En qué plataforma estás compilando en? Ventanas?
No te ves en el desmontaje para ver lo que realmente empuja en la pila? Es más, ¿los empuja en la pila, o de los registros de uso?