Como é que este programa funciona?

88
#include <stdio.h>

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

Ele exibe um 0!! Como isso é possível? Qual é o raciocínio?


Eu coloquei deliberadamente um %dna printfdeclaração para estudar o comportamento de printf.

lazer
fonte

Respostas:

239

Isso porque %despera um, intmas você forneceu um flutuador.

Use %e/ %f/ %gpara imprimir o flutuador.


Por que 0 é impresso: O número de ponto flutuante é convertido em doubleantes de enviar para printf. O número 1234,5 em representação dupla em little endian é

00 00 00 00  00 4A 93 40

A %dconsome um número inteiro de 32 bits, portanto, um zero é impresso. (Como um teste, você printf("%d, %d\n", 1234.5f);poderia obter na saída 0, 1083394560.)


Quanto a por que o floaté convertido para double, como o protótipo de printf é int printf(const char*, ...), de 6.5.2.2/7,

A notação de reticências em um declarador de protótipo de função faz com que a conversão do tipo de argumento pare após o último parâmetro declarado. As promoções de argumento padrão são executadas em argumentos finais.

e de 6.5.2.2/6,

Se a expressão que denota a função chamada tem um tipo que não inclui um protótipo, as promoções de inteiros são executadas em cada argumento e os argumentos que têm tipo floatsão promovidos para double. Eles são chamados de promoções de argumento padrão .

(Obrigado Alok por descobrir isso.)

Kennytm
fonte
4
+1 Melhor resposta. Ele responde por "padrão tecnicamente correto" e por "sua provável implementação" por quê.
Chris Lutz,
12
Acredito que você seja a única das 12 pessoas que realmente forneceu a resposta que ele procurava.
Gabe
11
Porque printfé uma função variada, e o padrão diz que para funções variadas, a floaté convertido doubleantes de passar.
Alok Singhal
8
Do padrão C: "A notação de reticências em um declarador de protótipo de função faz com que a conversão do tipo de argumento pare após o último parâmetro declarado. As promoções de argumento padrão são executadas em argumentos finais." e "... e os argumentos que possuem o tipo float são promovidos para double. Eles são chamados de promoções de argumento padrão ."
Alok Singhal
2
O uso do especificador de formato incorreto em printf()invoca o Comportamento Indefinido .
Prasoon Saurav
45

Tecnicamente falando, não existe o printf , cada biblioteca implementa seu próprio e, portanto, seu método de tentar estudar printfo comportamento fazendo o que você está fazendo não será muito útil. Você pode estar tentando estudar o comportamento do printfem seu sistema e, nesse caso, deve ler a documentação e olhar o código-fonte para ver printfse ele está disponível para sua biblioteca.

Por exemplo, no meu Macbook, recebo a saída 1606416304com o seu programa.

Dito isto, quando você passa a floatpara uma função variável, o floaté passado como a double. Portanto, seu programa é equivalente a ter sido declarado acomo um double.

Para examinar os bytes de a double, você pode ver esta resposta a uma pergunta recente aqui no SO.

Vamos fazer isso:

#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;
}

Quando executo o programa acima, obtenho:

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

Então, os primeiros quatro bytes do doubleacabaram sendo 0, que pode ser o motivo pelo qual você obteve 0como saída de sua printfchamada.

Para resultados mais interessantes, podemos mudar um pouco o programa:

#include <stdio.h>

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

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

Quando executo o programa acima no meu Macbook, obtenho:

42 1606416384

Com o mesmo programa em uma máquina Linux, obtenho:

0 1083394560
Alok Singhal
fonte
Por que você programou a impressão ao contrário? Estou esquecendo de algo? Se b é um int = 42 e '% d' é o formato inteiro, por que não é o segundo valor impresso, já que é a segunda variável nos argumentos printf? Isso é um erro de digitação?
Katastic Voyage
Provavelmente porque os intargumentos são passados ​​em registros diferentes dos doubleargumentos. printfcom %dleva o intargumento que é 42 e o segundo %dprovavelmente imprime lixo, pois não houve um segundo intargumento.
Alok Singhal
20

O %despecificador diz printfpara esperar um inteiro. Portanto, os primeiros quatro (ou dois, dependendo da plataforma) bytes do float são interpretados como um inteiro. Se acontecer de eles serem zero, um zero é impresso

A representação binária de 1234,5 é algo como

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

Com um compilador C que representa floatna verdade como valores duplos IEEE754, os bytes seriam (se eu não me enganasse)

01000000 10010011 01001010 00000000 00000000 00000000 00000000 00000000

Em um sistema Intel (x86) com pouco endianess (ou seja, o byte menos significativo vindo primeiro), essa sequência de bytes é revertida para que os primeiros quatro bytes sejam zero. Ou seja, o que printfimprime ...

Consulte este artigo da Wikipedia para representação de ponto flutuante de acordo com IEEE754.

MartinStettner
fonte
7

É por causa da representação de um flutuador em binário. A conversão para um número inteiro deixa com 0.

Shaihi
fonte
1
Você parece ser o único que entendeu o que ele estava pedindo. A menos que eu esteja me enganando, é claro.
Mizipzor
Concordo que a resposta é imprecisa e incompleta, mas não errada.
Shaihi
7

Porque você invocou um comportamento indefinido: você violou o contrato do método printf () mentindo para ele sobre seus tipos de parâmetro, então o compilador está livre para fazer o que quiser. Isso pode fazer com que a saída do programa seja "dksjalk é um ninnyhead !!!" e, tecnicamente, ainda estaria certo.

Kilian Foth
fonte
5

A razão é que essa printf()é uma função bastante idiota. Ele não verifica os tipos. Se você disser que o primeiro argumento é um int(e é com isso que você está falando %d), ele acredita em você e leva apenas os bytes necessários para um int. Nesse caso, supondo que sua máquina use quatro bytes inte oito bytes double(o floaté convertido em doubleinterno printf()), os primeiros quatro bytes de aserão apenas zeros e isso será impresso.

Gorpik
fonte
3

Ele não converterá automaticamente float em inteiro. Porque ambos possuem formatos de armazenamento diferentes. Portanto, se você deseja converter, use o typecasting (int).

#include <stdio.h>

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

Como você também o marcou com C ++, este código faz a conversão como você provavelmente espera:

#include <iostream.h>

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

Resultado:

1234.5 1234
Mizipzor
fonte
1

%d é decimal

%f é flutuar

veja mais destes aqui .

Você está obtendo 0 porque floats e inteiros são representados de forma diferente.

Filip Ekberg
fonte
1

Você só precisa usar o especificador de formato apropriado (% d,% f,% s, etc.) Com o tipo de dados relevante (int, float, string, etc.).

Muhammad Maqsoodur Rehman
fonte
A questão não é como consertar, mas por que funciona da maneira que funciona.
ya23
0

Você quer% f não% d

SC Madsen
fonte
0

ei, ele tinha que imprimir algo, então imprimiu um 0. Lembre-se que em C 0 está todo o resto!

cara gorda
fonte
1
Como é isso? Em C não existe tudo o mais.
Vlad,
se x for alguma coisa, então! x == 0 :)
chunkyguy
if (iGot == "tudo") imprimir "tudo"; senão imprime "nada";
nik
0

Não é um número inteiro. Tente usar %f.

tangrs
fonte
Acho que a pergunta é: por que ele simplesmente não converte o float em int e exibe "1234"?
Björn Pollex
não espere que c não deixe você fazer algo que não faz sentido lógico. Sim, muitas linguagens dariam o 1234 que você pode esperar, e talvez até mesmo algumas implementações de c não acho que esse comportamento esteja definido. C permite que você se enforque, é como o pai que permite que você tente crack de uma vez.
reexecutado em
Porque C foi projetado para ser flexível.
tangrs