89 votes

Pourquoi printf ("% f", 0)? donner un comportement indéfini?

La déclaration

printf("%f\n",0.0f);

affiche "0".

Cependant, la déclaration

printf("%f\n",0);

imprime des valeurs aléatoires.

Je me rends compte que je suis en présentant un certain type de comportement indéfini, mais je ne peux pas comprendre pourquoi spécifiquement.

Une valeur à virgule flottante dont tous les bits sont à 0 est toujours valide float de la valeur de 0.
float et int sont de la même taille sur ma machine (si c'est encore pertinent).

Pourquoi à l'aide d'un littéral entier au lieu d'une virgule flottante littérale en printf l'origine de ce comportement?

P. S. le même comportement peut être vu si j'utilise

int i = 0;
printf("%f\n", i);

122voto

Keith Thompson Points 85120

L' "%f" format nécessite un argument de type double. Vous êtes en lui donnant un argument de type int. C'est pourquoi le comportement est indéfini.

La norme ne garantit pas que tous les bits à zéro est une représentation valide de l' 0.0 (si elle l'est souvent), ou de tout double de la valeur, ou qu' int et double sont de la même taille (souvenez-vous double, pas float), ou, même si ils sont de la même taille, qu'ils sont passés comme arguments à une variadic fonction de la même manière.

Il peut arriver à "travailler" sur votre système. C'est la pire des symptômes possibles d'un comportement indéfini, car il est difficile de diagnostiquer l'erreur.

N1570 7.21.6.1 paragraphe 9:

... Si aucun argument n'est pas le type correct pour le correspondant spécification de conversion, le comportement est indéfini.

Les Arguments de type float sont promues double, ce qui est pourquoi printf("%f\n",0.0f) travaux. Les Arguments de types integer plus étroit que l' int sont promues int ou unsigned int. Ces règles de promotion (spécifié par N1570 6.5.2.2 paragraphe 6) ne permettent pas, dans le cas d' printf("%f\n", 0).

60voto

Zack Points 44583

Tout d'abord, comme évoqué dans plusieurs autres réponses mais pas, à mon avis, il définit assez clairement: Il n'est de travailler pour fournir un nombre entier dans la plupart des contextes où une fonction de la bibliothèque prend un double ou float argument. Le compilateur insère automatiquement une conversion. Par exemple, sqrt(0) est bien défini et ne se comportent exactement comme sqrt((double)0), et la même chose est vraie pour tout autre type entier expression-là.

printf est différent. Il est différent parce qu'il prend un nombre variable d'arguments. Son prototype de fonction est

extern int printf(const char *fmt, ...);

Par conséquent, lorsque vous écrivez

printf(message, 0);

le compilateur n'avons pas toutes les informations à propos de ce type printf s'attend à ce que le second argument. Il a seulement le type de l'argument expression, c'est - int, pour aller par. Par conséquent, contrairement à la plupart des fonctions de la bibliothèque, c'est sur, vous, le programmeur, afin de s'assurer que l'argument de la liste correspond aux attentes de la chaîne de format.

(Les compilateurs modernes pouvez les regarder dans un format de chaîne et de vous dire que vous avez une incompatibilité de type, mais ils ne vont pas commencer à insérer les conversions à accomplir ce que vous voulez dire, parce que mieux votre code doit pause maintenant, lorsque vous remarquerez, que des années plus tard, quand reconstruite avec un moins utile compilateur.)

Maintenant, l'autre moitié de la question: étant Donné que (int)0 et (float)0,0, sur la plupart des systèmes modernes, tous les deux représentés sur 32 bits qui sont toutes à zéro, pourquoi ne pas travailler de toute façon, par hasard? La norme C juste dit "ce n'est pas nécessaire pour le travail, vous êtes sur votre propre", mais permettez-moi de préciser les deux raisons les plus courantes pourquoi ça ne marcherait pas; ce sera probablement vous aider à comprendre pourquoi il n'est pas nécessaire.

Tout d'abord, pour des raisons historiques, lorsque vous passez une float grâce à une liste d'arguments variable, il obtient promu à l' double, ce qui, sur la plupart des systèmes modernes, est 64 bits de large. Donc, printf("%f", 0) passe seulement 32 zéro des bits à un appelé attend 64 d'entre eux.

La seconde, tout aussi importante raison est que à virgule flottante fonction des arguments peuvent être passés dans un autre lieu que les arguments entiers. Par exemple, la plupart des Processeurs distincts enregistrer des fichiers pour les entiers et les valeurs à virgule flottante, de sorte qu'il pourrait être une règle que les arguments de 0 à 4 go dans les registres r0 à r4 si elles sont des nombres entiers, mais f0 à f4 si ils sont à virgule flottante. Donc, printf("%f", 0) regarde dans le registre de la f1 pour que zéro, mais ce n'est pas là du tout.

13voto

πάντα ῥεῖ Points 15683

Pourquoi à l'aide d'un littéral entier au lieu d'un flotteur littéral de l'origine de ce comportement?

Parce qu' printf() n'ont pas de paramètres typés en plus de l' const char* formatstring comme le 1er. Il utilise un style c points de suspension (...) pour tous les autres.

C'est juste décide de la façon d'interpréter les valeurs y sont passés selon les types de mise en forme donnée dans la chaîne de format.

Vous avez le même genre de comportement indéfini comme lors de la tentative

 int i = 0;
 const double* pf = (const double*)(&i);
 printf("%f\n",*pf); // dereferencing the pointer is UB

13voto

Mark Ransom Points 132545

Habituellement, lorsque vous appelez une fonction qui s'attend à un double , mais que vous fournissez un int , le compilateur sera automatiquement converti en double . Cela ne se produit pas avec printf , car les types d'arguments ne sont pas spécifiés dans le prototype de fonction - le compilateur ne sait pas qu'une conversion doit être appliquée.

12voto

chux Points 13185

À l'aide d'un mal appariés printf() spécificateur "%f"et tapez (int) 0 conduit à un comportement indéfini.

Si une spécification de conversion n'est pas valide, le comportement est indéfini. C11dr §7.21.6.1 9

Candidat causes de l'UB.

  1. C'est UB par spec et la compilation est toujours d'aussi mauvaise humeur - 'lrdoe dit.

  2. double et int sont de tailles différentes.

  3. double et int peut transmettre leurs valeurs à l'aide de différents jeux (en général contre la FPU de la pile.)

  4. Un double 0.0 pourrait ne pas être défini par un zéro binaire. (rare)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X