Comment fonctionne ce programme?

88
#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", a);
    return 0;
}

Il affiche un 0!! Comment est-ce possible? Quel est le raisonnement?


J'ai délibérément mis un %ddans la printfdéclaration pour étudier le comportement de printf.

Lazer
la source

Réponses:

239

C'est parce que %ds'attend à un intmais vous avez fourni un flotteur.

Utilisez %e/ %f/ %gpour imprimer le flotteur.


Pourquoi 0 est imprimé: le nombre à virgule flottante est converti en doubleavant l'envoi vers printf. Le nombre 1234.5 en double représentation en petit boutiste est

00 00 00 00  00 4A 93 40

A %dconsomme un entier 32 bits, donc un zéro est imprimé. (En guise de test, vous printf("%d, %d\n", 1234.5f);pourriez obtenir la sortie 0, 1083394560.)


Quant à savoir pourquoi le floatest converti en double, comme le prototype de printf l'est int printf(const char*, ...), à partir du 6.5.2.2/7,

La notation des points de suspension dans un déclarateur de prototype de fonction provoque l'arrêt de la conversion de type d'argument après le dernier paramètre déclaré. Les promotions d'argument par défaut sont effectuées sur les arguments de fin.

et à partir du 6.5.2.2/6,

Si l'expression qui désigne la fonction appelée a un type qui n'inclut pas de prototype, les promotions d'entiers sont effectuées sur chaque argument et les arguments qui ont un type floatsont promus en double. On les appelle les promotions d'argument par défaut .

(Merci Alok d'avoir découvert cela.)

KennyTM
la source
4
+1 Meilleure réponse. Il répond à la fois au «pourquoi standard techniquement correct» et au «pourquoi de votre implémentation probable».
Chris Lutz
12
Je crois que vous êtes la seule des 12 personnes à avoir fourni la réponse qu'il recherchait.
Gabe
11
Parce que printfc'est une fonction variadique, et la norme dit que pour les fonctions variadiques, a floatest converti en doubleavant de passer.
Alok Singhal
8
De la norme C: "La notation des points de suspension dans un déclarateur de prototype de fonction provoque l'arrêt de la conversion de type d'argument après le dernier paramètre déclaré. Les promotions d'argument par défaut sont effectuées sur les arguments de fin." et "... et les arguments qui ont le type float sont promus au double. On les appelle les promotions d'argument par défaut ."
Alok Singhal
2
L'utilisation d'un spécificateur de format incorrect dans printf()appelle un comportement indéfini .
Prasoon Saurav
45

Techniquement parlant, il n'y a pas de the printf , chaque bibliothèque implémente la sienne, et par conséquent, votre méthode pour essayer d'étudier printfle comportement de ce que vous faites ne sera pas d'une grande utilité. Vous pourriez essayer d'étudier le comportement de printfsur votre système, et si tel est le cas, vous devriez lire la documentation et regarder le code source pour savoir printfs'il est disponible pour votre bibliothèque.

Par exemple, sur mon Macbook, j'obtiens la sortie 1606416304avec votre programme.

Cela dit, lorsque vous passez a floatà une fonction variadique, le floatest passé comme a double. Donc, votre programme équivaut à avoir déclaré en atant que fichier double.

Pour examiner les octets de a double, vous pouvez voir cette réponse à une question récente ici sur SO.

Faisons cela:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    unsigned char *p = (unsigned char *)&a;
    size_t i;

    printf("size of double: %zu, int: %zu\n", sizeof(double), sizeof(int));
    for (i=0; i < sizeof a; ++i)
        printf("%02x ", p[i]);
    putchar('\n');
    return 0;
}

Lorsque j'exécute le programme ci-dessus, j'obtiens:

size of double: 8, int: 4
00 00 00 00 00 4a 93 40 

Ainsi, les quatre premiers octets de la doublese sont avérés être 0, ce qui peut être la raison pour laquelle vous avez obtenu 0la sortie de votre printfappel.

Pour des résultats plus intéressants, nous pouvons modifier un peu le programme:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    int b = 42;

    printf("%d %d\n", a, b);
    return 0;
}

Lorsque j'exécute le programme ci-dessus sur mon Macbook, j'obtiens:

42 1606416384

Avec le même programme sur une machine Linux, j'obtiens:

0 1083394560
Alok Singhal
la source
Pourquoi avez-vous programmé l'impression à l'envers? Est-ce que je manque quelque chose? Si b est un int = 42 et '% d' est le format entier, pourquoi n'est-ce pas la deuxième valeur imprimée, puisque c'est la deuxième variable dans les arguments printf? Est-ce une faute de frappe?
Katastic Voyage
Probablement parce que les intarguments sont passés dans des registres différents des doublearguments. printfwith %dprend l' intargument qui est 42 et le second %dimprime probablement des fichiers indésirables puisqu'il n'y avait pas de second intargument.
Alok Singhal
20

Le %dspécificateur dit printfd'attendre un entier. Ainsi, les quatre premiers octets (ou deux, selon la plate-forme) du float sont interprétés comme un entier. S'ils se trouvent être zéro, un zéro est imprimé

La représentation binaire de 1234,5 est quelque chose comme

1.00110100101 * 2^10 (exponent is decimal ...)

Avec un compilateur C qui représente en floatfait des valeurs doubles IEEE754, les octets seraient (si je ne me trompais pas)

01000000 10010011 01001010 00000000 00000000 00000000 00000000 00000000

Sur un système Intel (x86) avec peu d'extrémité (c'est-à-dire l'octet le moins significatif venant en premier), cette séquence d'octets est inversée de sorte que les quatre premiers octets sont à zéro. Autrement dit, ce qui printfs'imprime ...

Consultez cet article de Wikipedia pour la représentation en virgule flottante selon IEEE754.

MartinStettner
la source
7

C'est à cause de la représentation d'un flottant en binaire. La conversion en entier le laisse avec 0.

Shaihi
la source
1
Vous semblez être le seul à avoir compris ce qu'il demandait. A moins que je ne me trompe bien sûr.
Mizipzor
Je reconnais que la réponse est inexacte et incomplète, mais pas fausse.
Shaihi
7

Parce que vous avez invoqué un comportement indéfini: vous avez violé le contrat de la méthode printf () en lui mentant sur ses types de paramètres, de sorte que le compilateur est libre de faire ce qu'il veut. Cela pourrait faire sortir le programme "dksjalk est un ninnyhead !!!" et techniquement, ce serait toujours vrai.

Kilian Foth
la source
5

La raison en est que printf()c'est une fonction assez stupide. Il ne vérifie pas du tout les types. Si vous dites que le premier argument est un int(et c'est ce avec quoi vous dites %d), il vous croit et il ne prend que les octets nécessaires pour un fichier int. Dans ce cas, en supposant que votre machine utilise quatre intet huit octets double(le floatest converti en un à l' doubleintérieur printf()), les quatre premiers octets de aseront juste des zéros, et cela sera imprimé.

Gorpik
la source
3

Il ne convertira pas automatiquement le flottant en entier. Parce que les deux ont un format de stockage différent. Donc, si vous souhaitez convertir, utilisez le typage (int).

#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", (int)a);
    return 0;
}
sganesh
la source
2

Puisque vous l'avez également marqué avec C ++, ce code effectue la conversion comme vous vous y attendez probablement:

#include <iostream.h>

int main() {
    float a = 1234.5f;
    std::cout << a << " " << (int)a << "\n";
    return 0;
}

Production:

1234.5 1234
Mizipzor
la source
1

%d est décimal

%f est flottant

voir plus de ceux-ci ici .

Vous obtenez 0 car les flottants et les entiers sont représentés différemment.

Filip Ekberg
la source
1

Il vous suffit d'utiliser le spécificateur de format approprié (% d,% f,% s, etc.) avec le type de données approprié (int, float, string, etc.).

Muhammad Maqsoodur Rehman
la source
La question n'est pas de savoir comment y remédier, mais pourquoi cela fonctionne comme cela.
ya23
0

Vous voulez% f pas% d

SC Madsen
la source
0

hey il fallait imprimer quelque chose donc il a imprimé un 0. Rappelez-vous en C 0 c'est tout le reste!

chunkyguy
la source
1
Comment ça se passe? En C, tout le reste n'existe pas.
Vlad
si x est quelque chose, alors! x == 0 :)
chunkyguy
if (iGot == "tout") affiche "tout"; sinon imprimer "rien";
nik
0

Ce n'est pas un entier. Essayez d'utiliser %f.

tangrs
la source
Je suppose que la question est, pourquoi ne convertit-il pas simplement le float en int et affiche "1234"?
Björn Pollex
ne vous attendez pas à ce que c ne vous laisse pas faire quelque chose qui n'a aucun sens logique. Oui, de nombreux langages donneraient les 1234 auxquels vous pouvez vous attendre, et peut-être même certaines implémentations de c ne penseront-elles pas que ce comportement est défini. C vous permet de vous pendre, c'est comme le parent qui vous permet d'essayer de craquer pour l'enfer.
rediffusé
Parce que C est conçu pour être flexible.
tangrs