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.

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.)


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

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.


7

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


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.


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.


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

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

1

%d é decimal

%f é flutuar

veja mais destes aqui .

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


1

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


A questão não é como consertar, mas por que funciona da maneira que funciona.
ya23


0

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


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.