2010-09-08 40 views
0
#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; 
} 

donne (Linux, gcc, x64)printf: comment expliquer résultat corrompu

 
0 0.000116 0 0.000116 
      ^^^^^^^^ odd, since the memory for resi is zeroed 

En fait, compilé avec g ++, il donne des résultats aléatoires au lieu de la deuxième 0.

Je comprends que je a donné des spécificateurs invalides à printf et qu'il déclenche un comportement non défini non défini, mais je me demande pourquoi cette corruption spécifique se produit, puisque long long et double ont la même taille.

+3

'il déclenche un comportement non spécifié' ... nuff dit: P – pmg

+1

@pmg: Le comportement est * indéfini *, non non spécifié (7.19.6.1.9). Il existe une différence. –

+2

@Martin: Il ne peut pas être expliqué par la norme, mais il peut encore être expliqué; le comportement est indéfini, non non déterministe. C'est une vraie question, mais pas sur le langage C. –

Répondre

4

Je reçois les mêmes résultats que sur ma machine (Mac OS X, donc AMD/Linux ABI). Les paramètres à virgule flottante sont transmis dans les registres XMM et les paramètres entiers dans les registres d'entiers. Lorsque printf les attrape en utilisant va_arg, il tire à partir de XMM lorsqu'il voit le format %f, et des autres registres lorsqu'il voit %lld. Voici le démontage de votre programme compilé (-O0) sur ma machine:

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 

Là, vous pouvez voir ce qui se passe - la chaîne de format est passé dans %rdi, vos paramètres sont passés (dans l'ordre) dans: %xmm0 , %xmm1, %rsi et %rdx. Lorsque printf les obtient, il les affiche dans un ordre différent (l'ordre spécifié dans votre chaîne de format). Cela signifie qu'il les fait apparaître: %rsi, %xmm0, %rdx, %xmm1, donnant les résultats que vous voyez. Le 2 dans %eax est d'indiquer le nombre d'arguments à virgule flottante passés.

Edit:

est ici une version optimisée - dans ce cas, le code plus court pourrait être plus facile à comprendre. L'explication est la même que ci-dessus, mais avec un bruit un peu moins chaud. La valeur en virgule flottante est chargé par le movsd en ligne 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 
9

C'est parce que sous le x86_64 C conventions d'appel sur votre plate-forme, les deux premiers arguments à virgule flottante sont passés dans xmm0 et xmm1, et les deux premiers les arguments entiers sont passés en GPR (rsi et rdx si vous utilisez Linux ou OS X), quel que soit l'ordre dans lequel ils apparaissent.

Vous êtes confus car vous vous attendez à ce que les paramètres soient transmis en mémoire; ils ne le sont pas.

1
  • Le premier nombre doit être une valeur élevée car vous transmettez un double en entier. Cela devrait être% f.
  • Quelle est la période après le "0" dans "resi"? Cela va le transformer en double, donc vous essayez de charger un double dans un entier. Cela devrait vous donner un avertissement de compilateur.
  • Certaines implémentations peuvent être basées sur des registres, donc comme vous gâchiez les types d'arguments, cela devient confus.

Sur quelle plateforme compilez-vous? Les fenêtres?

Avez-vous regardé le démontage pour voir ce qu'il pousse réellement sur la pile? Est-ce qu'il les pousse même sur la pile ou utilise des registres?

+0

Ok, humour moi. Qui a voté cela, et pourquoi? – EboMike

+3

Le spécificateur de format '% f'' printf' est pour un double, pas un flottant (c'est différent de 'scanf'); Les arguments à virgule flottante des fonctions va_arg sont implicitement promus en double. '% Lf' est pour le double long. –

+0

J'ai écrit accidentellement la période et l'ai ensuite enlevée (cela ne devrait d'ailleurs pas avoir d'importance). C'est Linux, amd64, compilé avec gcc. (le downvote n'était pas le mien). – Artefacto