2015-04-16 13 views
5

Poznałem problem kiedy przetestować printf Funkcja:Jak działa funkcja printf w C?

pierwszy napisać kod tak:

int main(void) 
{ 
    char a = 'a'; 
    printf("a = %f\n", a); 
    return 0; 
} 

Wyjście jest

enter image description here

A potem napisać kod:

int main(void) 
{ 
    float b = 'a'; 
    printf("b = %f\n", b); 
    return 0; 
} 

Na zewnątrz put jest

enter image description here

A potem napisać kod:

int main(void) 
{ 
    char a = 'a'; 
    float b = 'a'; 
    printf("b = %f\n", b); 
    printf("a = %f\n", a); 
    return 0; 
} 

wyjście jest

enter image description here

Więc jestem zdezorientowany, dlaczego w pierwszym programie a = 0.000000 iw trzeci program: a = 97.000000?
Jak działa funkcja printf()?
Jak działa symbol %f, %d?

+1

RTFM, np. przeczytaj [printf (3)] (http://man7.org/linux/man-pages/man3/printf.3.html) i/lub [printf] (http://en.cppreference.com/w/c/io/fprintf) –

+0

Nie masz pojęcia o printf, ale 'float b = 'a';' jest w porządku dla ciebie? ;) – sebastian

+0

@sebastian przepraszam, nie wiem co masz na myśli. –

Odpowiedz

6

Aktualizacja: po zrobieniu kilka badań na ten temat, wydaje się, że różnice między reprezentacjami pamięci float i int nie są odpowiedzialni za zachowanie tych trzech programów.

Sprawdziłem kod obiektowy dla trzeciego programu i znalazłem przyczynę dziwnego zachowania: argumenty zmiennoprzecinkowe są wysyłane do innego rejestru/stosu niż liczby całkowite. I printf polega na tym i wyszukuje je w innych lokalizacjach niż te używane przez adresata printf (tj. Metoda main) umieszcza argumenty.

Oto odnośny demontaż trzeci program (dla architektury x86_64):

0000000100000f18 leaq 0x71(%rip), %rdi  ## literal pool for: "b = %f\n" 
0000000100000f1f movsd 0x61(%rip), %xmm0  ## b variable gets sent to xmm0 
0000000100000f27 movl $0x0, -0x4(%rbp) 
0000000100000f2e movb $0x61, -0x5(%rbp)  ## a variable gets placed on the callee stack 
0000000100000f32 movsd %xmm0, -0x10(%rbp) 
0000000100000f37 movsd -0x10(%rbp), %xmm0 
0000000100000f3c movb $0x1, %al 
0000000100000f3e callq 0x100000f66    ## symbol stub for: _printf 
0000000100000f43 leaq 0x4e(%rip), %rdi  ## literal pool for: "a = %f\n" 
0000000100000f4a movsbl -0x5(%rbp), %esi 
0000000100000f4e movl %eax, -0x14(%rbp) 
0000000100000f51 movb $0x0, %al 
0000000100000f53 callq 0x100000f66    ## symbol stub for: _printf 

I printf polega na tym, że zakłada, że ​​wywoływany umieścił %f argumenty w xmm0/xmm1/etc rejestrów, i zachowanie trzech programów jest tak:

  1. w pierwszym programie printf poszukuje %f argumentu w xmm0 zarejestrować Howe ver jak jesteśmy na początku programu, rejestr jest czysty i main umieścił a do eax, więc xmm0 posiada wartość zero, a to co printf drukuje
  2. w drugim programie main poprawnie umieszcza b w xmm0 i printf zabiera go stamtąd, drukowanie poprawną wartość
  3. w trzecim programie ze względu na fakt, że b jest drukowany jako pierwszy rejestr xmm0 obejmie tę wartość, a ponieważ printf nie robi bałagan w rejestrze, kiedy to drugi raz wywołuje go ponownie z xmm0, który pozostał nienaruszony ter pierwsze wywołanie printf.

Tak więc chodzi o konwencje wywołującego/wywołującego, w których liczby całkowite i przepływy są wysyłane przez dzwoniącego i skąd odbiorca próbuje je odebrać.


Oryginalny odpowiedź: W pierwszym programie, który próbujesz wydrukować pływaka, ale trzeba zdać int (char jest mniejszy int). Z uwagi na to, że int i float mają różne reprezentacje binarne, int 97 (odpowiadający znakowi "a") odpowiada bardzo niewielkiemu floatowi: 1.36E-43, który zostaje wydrukowany jako zero.

Tutaj jest binarna reprezentacja 97 (kompilator rozszerza węgiel odbarwiający 1 bajt argumentu 4 bajtów podczas wywoływania funkcji)
00000000 00000000 00000000 01100001

IEEE 754 jest standardowym formatem binarnym pływaka/podwójnego Liczby można grać za pomocą konwertera online here, a można zobaczyć, jak ta sama liczba binarna ma różne wartości, gdy jest interpretowana jako int lub float.

+0

Dziękuję za odpowiedź. Ale jak wytłumaczyć trzeci program? –

+1

Cóż, myślę, że jest to jedno z tych nieokreślonych zachowań, które pojawiają się, gdy nie pasujesz do ciągu formatów i listy argumentów przekazanych do printf. Ciesz się, że nie zdarzyło ci się zderzyć :) – Cristik

+0

Myślę, że to wyjście bufora wyjściowego do wyjścia w trzecim programie. Ponieważ kiedy wymieniam dwa zdania printf, wynik wyniósł a = 0,000000 b = 97.000000. –

0

%f służy do float. Musisz użyć znaków %c.

Jeśli używasz

printf("a = %c\n", a); 

Dostaniesz charakter.

Tak więc, jeśli zmieni swój pierwszy kod do

int main(void) 
{ 
    char a = 'a'; 
    printf("a = %c\n", a); 
    return 0; 
} 

Dostaniesz wyjścia jak

a 
+0

Wiem. Ale chcę wiedzieć, jaka jest różnica między% f i% c? –

+0

@ Mr.CodeMonkey, możesz sprawdzić wyjście, które mam [tutaj] (http://ideone.com/DOVli0) –

+0

Och, dlaczego? Używam clodeblocks13.12. Moje os to Windows7 64bit. Czy wiesz, dlaczego nasze wyniki są różne? –

1

% f jest dla Float % c dla znaków

The 97 which you have got is the ASCII value for 'a' 
5

The %f tutaj reprezentuje token zastępczy dla float.

Aby zamienić znak, należy uzyskać %c.

Here is a list informuje, jaki jest odpowiedni token zastępczy dla każdego typu.

+0

Wiem o tym. Ale chcę wiedzieć, kiedy używam% f zamiast% c, co zrobi funkcja printf? Jak odczytuje rejestr? –

+0

@ Mr.CodeMonkey, to co robisz to niezdefiniowane zachowanie, więc _nie_ rzeczy_ jest prawidłowym wynikiem, aż do całkowitego zniszczenia wszechświata. Innymi słowy, nie rób tego :-) – paxdiablo

0

Różnica między

printf("%f\n, 97.0); 

i

printf("%c\n, 'a'); 

jest to, że printf odczytuje parametry ze stosu w oparciu o %X dajesz, i interpretuje je (na wyświetlaczu) jako takie .

Dla %c printf oczekuje char jako parametr, więc będzie czytać Char (bajt, ale często faktycznie int, jest zależna od implementacji) i wyświetla je (wyświetla mniej znaczący bajt, jeśli int jest).

Dla printf oczekuje float (którego rozmiar w bajtach to sizeof(float), zwykle 4 bajty na procesorach gcc/Intel).

Jeśli skompilować z gcc należy wybrać opcję -Wall które dałyby ostrzeżenie, gdy format, %X i typ parametru nie pasują.

+0

Rozumiem, co powiedziałeś. Ale nie może wyjaśnić mojego programu. Kiedy definiuję jak char a = "a"; i printf ("a =% f \ n", a); Czy printf odczytuje 4 bajty danych zaczynając od adresu a? Jeśli tak, wynik nie może wynosić 0, czy nie? –

+0

Zapoznaj się z doskonałą odpowiedzią od Cristika (powyżej), która wyjaśnia, dlaczego w tym przypadku otrzymujesz '0'. –

1

Zgodnie z najnowszym standardem "C" jest to niezdefiniowane zachowanie. Sprawdź 7.21.6.1 pkt 9 z c standardowego projektu.

Jeżeli specyfikacja konwersji jest nieważny, zachowanie jest undefined.282) Jeżeli argument jest odpowiedniego typu do odpowiadającej specyfikacji konwersji w , zachowanie jest nieokreślona.

Kiedy mówi się, że coś jest nieokreślone, wszystko jest możliwe, a zachowanie może się różnić w zależności od kompilatora. "C" pozwól ci przeciąć stopę siekierą, ale to nie ja powinieneś to zrobić.