Jak działa ten program?

88
#include <stdio.h>

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

Wyświetla 0!! Jak to możliwe? Jaki jest tego powód?


Celowo umieściłem %dw printfoświadczeniu znak, aby zbadać zachowanie printf.

Lazer
źródło

Odpowiedzi:

239

To dlatego, że %doczekuje, intale dostarczyłeś pływaka.

Użyj %e/ %f/, %gaby wydrukować pływak.


O tym, dlaczego wypisywane jest 0: Liczba zmiennoprzecinkowa jest konwertowana na doubleprzed wysłaniem do printf. Liczba 1234,5 w podwójnej reprezentacji w little endian to

00 00 00 00  00 4A 93 40

A %dzużywa 32-bitową liczbę całkowitą, więc drukowane jest zero. (W ramach testu możesz printf("%d, %d\n", 1234.5f);uzyskać wynik 0, 1083394560.)


Jeśli chodzi o powód, dla którego floatjest konwertowany na double, jak prototyp printf int printf(const char*, ...), z 6.5.2.2/7,

Notacja wielokropka w deklaratorze prototypu funkcji powoduje zatrzymanie konwersji typu argumentu po ostatnim zadeklarowanym parametrze. Domyślne promocje argumentów są wykonywane na argumentach końcowych.

i od 6.5.2.2/6,

Jeśli wyrażenie oznaczające wywoływaną funkcję ma typ, który nie zawiera prototypu, promocje liczb całkowitych są wykonywane dla każdego argumentu, a argumenty, które mają typ, floatsą promowane do double. Nazywa się to domyślnymi promocjami argumentów .

(Dzięki Alok, że się o tym dowiedziałeś.)

kennytm
źródło
4
+1 Najlepsza odpowiedź. Odpowiada zarówno „standardowo poprawne technicznie”, dlaczego i dlaczego „prawdopodobne wdrożenie”.
Chris Lutz,
12
Uważam, że jesteś jedyną z 12 osób, które faktycznie udzieliły odpowiedzi, której szukał.
Gabe
11
Ponieważ printfjest to funkcja wariadyczna, a standard mówi, że w przypadku funkcji wariadycznych przed przekazaniem a floatjest konwertowany na double.
Alok Singhal
8
Ze standardu C: „Notacja wielokropka w deklaratorze prototypu funkcji powoduje zatrzymanie konwersji typu argumentu po ostatnim zadeklarowanym parametrze. Domyślne promocje argumentów są przeprowadzane na argumentach końcowych.” i „... a argumenty, które mają typ zmiennoprzecinkowy, są promowane do wartości podwójnej. Nazywa się to domyślnymi promocjami argumentów .
Alok Singhal
2
Użycie nieprawidłowego specyfikatora formatu w printf()wywołuje niezdefiniowane zachowanie .
Prasoon Saurav
45

Z technicznego punktu widzenia nie ma to , każda biblioteka realizuje swoje własne, a zatem metoda próbuje badania jest zachowanie robiąc to, co robisz, nie będzie znacznie użytkowania. Możesz próbować przestudiować zachowanie w swoim systemie, a jeśli tak, powinieneś przeczytać dokumentację i spojrzeć na kod źródłowy printfprintfprintfprintf czy jest dostępny dla Twojej biblioteki.

Na przykład na moim Macbooku otrzymuję dane wyjściowe 1606416304z twoim programem.

Powiedziawszy to, kiedy przekazujesz a floatdo funkcji wariadycznej, floatjest ona przekazywana jako a double. Więc twój program jest równoważny zadeklarowaniu ajako double.

Aby zbadać bajty a double, możesz zobaczyć tę odpowiedź na ostatnie pytanie tutaj na SO.

Zróbmy to:

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

Po uruchomieniu powyższego programu otrzymuję:

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

Tak więc pierwsze cztery bajty doubleokazały się równe 0, co może być powodem, dla którego otrzymałeś 0wynik swojego printfwywołania.

Aby uzyskać ciekawsze wyniki, możemy nieco zmienić program:

#include <stdio.h>

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

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

Po uruchomieniu powyższego programu na moim Macbooku otrzymuję:

42 1606416384

Z tym samym programem na komputerze z systemem Linux otrzymuję:

0 1083394560
Alok Singhal
źródło
Dlaczego zaprogramowałeś drukowanie od tyłu? Czy coś mi brakuje? Jeśli b to int = 42, a „% d” jest formatem liczby całkowitej, dlaczego nie jest to druga drukowana wartość, skoro jest to druga zmienna w argumentach printf? Czy to pomyłka?
Katastic Voyage
Prawdopodobnie dlatego, że intargumenty są przekazywane w innych rejestrach niż doubleargumenty. printfwith %dprzyjmuje intargument, który %dwynosi 42, a drugi prawdopodobnie wypisuje śmieci, ponieważ nie było drugiego intargumentu.
Alok Singhal
20

Specyfikator %dmówi, printfże ma oczekiwać liczby całkowitej. Zatem pierwsze cztery (lub dwa, w zależności od platformy) bajty zmiennej zmiennoprzecinkowej są interpretowane jako liczba całkowita. Jeśli zdarzy się, że są równe zero, drukowane jest zero

Binarna reprezentacja 1234,5 jest podobna do

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

W przypadku kompilatora C, który reprezentuje w floatrzeczywistości podwójne wartości IEEE754, bajty byłyby (gdybym się nie pomylił)

01000000 10010011 01001010 00000000 00000000 00000000 00000000 00000000

W systemie Intel (x86) z małą endianess (tj. Najmniej znaczący bajt pojawia się jako pierwszy), ta sekwencja bajtów jest odwracana, tak że pierwsze cztery bajty są zerowe. To znaczy, co się printfdrukuje ...

Zobacz ten artykuł w Wikipedii dla reprezentacji zmiennoprzecinkowej zgodnie z IEEE754.

MartinStettner
źródło
7

Dzieje się tak z powodu reprezentacji liczby zmiennoprzecinkowej w systemie binarnym. Konwersja na liczbę całkowitą pozostawia 0.

Shaihi
źródło
1
Wydaje się, że jesteś jedynym, który rozumiał, o co prosił. Chyba że się mylę, oczywiście.
Mizipzor
Zgadzam się, że odpowiedź jest niedokładna i niekompletna, ale nie jest błędna.
Shaihi,
7

Ponieważ wywołałeś niezdefiniowane zachowanie: naruszyłeś kontrakt metody printf (), okłamując ją co do typów parametrów, więc kompilator może robić, co mu się podoba. Może to spowodować, że na wyjściu programu pojawi się komunikat „dksjalk is a ninnyhead !!!” i technicznie nadal byłoby właściwe.

Kilian Foth
źródło
5

Powodem jest to, że printf()jest to dość głupia funkcja. W ogóle nie sprawdza typów. Jeśli powiesz, że pierwszym argumentem jest an int(i to właśnie z nim mówisz %d), to on ci wierzy i zajmuje tylko bajty potrzebne do int. W tym przypadku, zakładając, że twój komputer używa czterobajtów inti ośmiu bajtów double( floatjest konwertowany na doublewewnętrzny printf()), pierwsze cztery bajty abędą po prostu zerami, a to zostanie wydrukowane.

Gorpik
źródło
3

Nie konwertuje automatycznie liczby zmiennoprzecinkowej na liczbę całkowitą. Ponieważ oba mają inny format przechowywania. Więc jeśli chcesz konwertować, użyj (int) typecasting.

#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", (int)a);
    return 0;
}
sganesh
źródło
2

Ponieważ otagowałeś go również C ++, ten kod wykonuje konwersję zgodnie z oczekiwaniami:

#include <iostream.h>

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

Wynik:

1234.5 1234
Mizipzor
źródło
1

%d jest dziesiętna

%f jest pływający

zobacz więcej tutaj .

Otrzymujesz 0, ponieważ liczby zmiennoprzecinkowe i liczby całkowite są reprezentowane inaczej.

Filip Ekberg
źródło
1

Wystarczy użyć odpowiedniego specyfikatora formatu (% d,% f,% s, itd.) Z odpowiednim typem danych (int, float, string itp.).

Muhammad Maqsoodur Rehman
źródło
Pytanie nie brzmi, jak to naprawić, ale dlaczego działa tak, jak działa.
ya23
0

hej, musiało coś wydrukować, więc wypisało 0. Pamiętaj, że w C 0 jest wszystko inne!

chunkyguy
źródło
1
Jak to się dzieje? W C nie ma czegoś takiego jak wszystko inne.
Vlad
jeśli x jest czymś, to! x == 0 :)
chunkyguy
if (iGot == "wszystko") wypisuje "wszystko"; jeszcze drukuje "nic";
nik
0

To nie jest liczba całkowita. Spróbuj użyć %f.

tangrs
źródło
Wydaje mi się, że pytanie brzmi, dlaczego po prostu nie konwertuje liczby zmiennoprzecinkowej na int i nie wyświetla „1234”?
Björn Pollex
nie oczekuj, że c nie pozwoli ci zrobić czegoś, co nie ma logicznego sensu. Tak, wiele języków dałoby 1234, którego możesz się spodziewać, a może nawet niektóre implementacje c nie sądzę, że to zachowanie jest zdefiniowane. C pozwala ci się powiesić, jest jak rodzic, który pozwala ci się złamać.
powtórz
Ponieważ C ma być elastyczny.
tangrs