Dlaczego printf ("%f", 0); daje nieokreślone zachowanie?
Oświadczenie
printf("%f\n",0.0f);
Drukuje 0.
Jednak twierdzenie
printf("%f\n",0);
Wyświetla losowe wartości.
Zdaję sobie sprawę, że przejawiam jakieś nieokreślone zachowanie, ale nie mogę zrozumieć dlaczego.Wartość zmiennoprzecinkowa, w której wszystkie bity są równe 0, jest nadal poprawną {[3] } z wartością 0.float
i int
są tego samego rozmiaru na mojej maszynie(jeśli to w ogóle istotne).
Dlaczego używa się liczby całkowitej zamiast zmiennoprzecinkowej dosłowne w printf
powodują takie zachowanie?
P. S. to samo zachowanie można zobaczyć, jeśli używam
int i = 0;
printf("%f\n", i);
10 answers
Format "%f"
wymaga argumentu typu double
. Podajesz argument typu int
. Dlatego zachowanie jest nieokreślone.
Standard nie gwarantuje, że wszystkie bity-zero są poprawną reprezentacją 0.0
(choć często tak jest), lub dowolnej wartości double
, lub że int
i double
są tego samego rozmiaru (pamiętaj, że to double
, a nie float
), lub nawet jeśli są tego samego rozmiaru, to są przekazywane jako argumenty do funkcji zmiennej w ten sam sposób.
To może zdarza się, że" działa " na Twoim systemie. To najgorszy możliwy objaw nieokreślonego zachowania, ponieważ utrudnia zdiagnozowanie błędu.
N1570 7.21.6.1 pkt 9:
... Jeśli jakikolwiek argument nie jest prawidłowym typem dla odpowiedniego Specyfikacja konwersji, zachowanie jest niezdefiniowane.
Argumenty typu float
są promowane do double
, dlatego printf("%f\n",0.0f)
Działa. Argumenty typów całkowitych węższe niż int
są promowane do int
lub do unsigned int
. Niniejsze zasady promocji (określone w N1570 6.5.2.2 pkt 6) nie pomagają w przypadku printf("%f\n", 0)
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 23:55:03
Po pierwsze, jak wspomniano w kilku innych odpowiedziach, ale moim zdaniem nie jest to wystarczająco jasne: Działa, aby zapewnić liczbę całkowitą w większości kontekstów, w których funkcja biblioteczna przyjmuje argument double
lub float
. Kompilator automatycznie wstawia konwersję. Na przykład, sqrt(0)
jest dobrze zdefiniowany i będzie zachowywał się dokładnie tak, jak sqrt((double)0)
, i to samo dotyczy każdego innego wyrażenia typu integer używanego tam.
printf
to co innego. Jest inny, ponieważ pobiera zmienną liczbę argumentów. Jego prototypem funkcji jest
extern int printf(const char *fmt, ...);
Dlatego kiedy piszesz
printf(message, 0);
Kompilator nie ma żadnych informacji o tym, jakiego typu printf
oczekuje , że drugi argument będzie. Ma tylko typ wyrażenia argumentu, który jest int
, aby przejść przez. Dlatego, w przeciwieństwie do większości funkcji bibliotecznych, to Ty, programista, musisz upewnić się, że lista argumentów odpowiada oczekiwaniom ciągu formatującego.
(Modern Kompilatory mogą przyjrzeć się ciągowi formatowemu i powiedzieć ci, że masz niedopasowanie typu, ale nie zaczną wstawiać konwersji, aby osiągnąć to, co miałeś na myśli, ponieważ lepiej, aby Twój kod zepsuł się teraz, kiedy zauważysz, niż lata później, gdy został przebudowany z mniej pomocnym kompilatorem.)
Druga połowa pytania brzmiała: biorąc pod uwagę, że (int) 0 I (float) 0.0 są, w większości nowoczesnych systemów, oba reprezentowane jako 32 bity, z których wszystkie są zerowe, dlaczego to nie działa w każdym razie, przez przypadek? Standard C mówi po prostu "to nie jest wymagane do pracy, jesteś zdany na siebie", ale pozwól mi wyjaśnić dwa najczęstsze powody, dla których to nie działa; to prawdopodobnie pomoże Ci zrozumieć dlaczego to nie jest wymagane.
Po pierwsze, ze względów historycznych, kiedy przekazujesz {[3] } przez zmienną listę argumentów, otrzymuje awans do double
, co w większości nowoczesnych systemów jest 64 bity szerokie. Więc printf("%f", 0)
przekazuje tylko 32 bity zerowe do callee 64 z nich.
Drugim, równie istotnym powodem jest to, że argumenty funkcji zmiennoprzecinkowych mogą być przekazywane w innym miejscu niż argumenty liczb całkowitych. Na przykład, większość procesorów ma oddzielne pliki rejestrów dla liczb całkowitych i wartości zmiennoprzecinkowych, więc może być regułą, że argumenty od 0 do 4 trafiają do rejestrów od r0 do r4, jeśli są liczbami całkowitymi, ale od f0 do f4, jeśli są zmiennoprzecinkowe. Więc printf("%f", 0)
wygląda w rejestrze f1 na to zero, ale w ogóle go tam nie ma.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 19:20:42
Zwykle gdy wywołujesz funkcję oczekującą double
, ale podajesz int
, kompilator automatycznie przekształci się w double
dla Ciebie. Tak się nie dzieje z printf
, ponieważ typy argumentów nie są określone w prototypie funkcji - kompilator nie wie, że należy zastosować konwersję.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 18:28:08
Dlaczego użycie liczby całkowitej zamiast liczby zmiennoprzecinkowej powoduje takie zachowanie?
Ponieważ printf()
nie ma wpisanych parametrów poza const char* formatstring
jako pierwszy. Używa elipsy w stylu c (...
) dla całej reszty.
To po prostu decyduje, jak interpretować przekazywane tam wartości zgodnie z typami formatowania podanymi w łańcuchu formatowania.
Zachowywałbyś się tak samo, jak przy próbie
int i = 0;
const double* pf = (const double*)(&i);
printf("%f\n",*pf); // dereferencing the pointer is UB
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 19:09:04
Użycie źle dopasowanego specyfika printf()
"%f"
i typu (int) 0
prowadzi do niezdefiniowanego zachowania.
Jeśli specyfikacja konwersji jest nieprawidłowa, zachowanie jest niezdefiniowane. C11dr §7.21.6.1 9
Kandydaci do UB.
To jest UB per spec, a kompilacja jest ornery-powiedział nuf.
double
i {[4] } są różnej wielkości.double
iint
mogą przekazywać swoje wartości za pomocą różnych stosów (ogólne vs. FPU stack.)-
A
double 0.0
może nie być zdefiniowany przez cały zero bitowy wzorzec. (rzadko)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 20:27:12
Jest to jedna z tych wspaniałych możliwości, aby dowiedzieć się z ostrzeżeń kompilatora.
$ gcc -Wall -Wextra -pedantic fnord.c
fnord.c: In function ‘main’:
fnord.c:8:2: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
printf("%f\n",0);
^
Lub
$ clang -Weverything -pedantic fnord.c
fnord.c:8:16: warning: format specifies type 'double' but the argument has type 'int' [-Wformat]
printf("%f\n",0);
~~ ^
%d
1 warning generated.
Więc, printf
produkuje niezdefiniowanego zachowania, ponieważ przekazujesz mu niezgodny typ argumentu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-27 15:46:38
Nie wiem, co jest mylące.
Twój format oczekuje double
; zamiast tego podajesz int
.
To, czy te dwa typy mają tę samą szerokość bitów, jest całkowicie nieistotne, z wyjątkiem tego, że może to pomóc uniknąć wyjątków od naruszenia twardej pamięci z uszkodzonego kodu, takiego jak ten.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 18:25:58
"%f\n"
gwarantuje przewidywalny wynik tylko wtedy, gdy drugi parametr printf()
mA typ double
. Następnie dodatkowe argumenty funkcji wariacyjnych są przedmiotem domyślnej promocji argumentów. Argumenty liczb całkowitych należą do promocji liczby całkowitej, która nigdy nie skutkuje wartościami zmiennoprzecinkowymi. I float
parametry są promowane do double
.
Na koniec: standard pozwala na to, aby drugi argument był or float
lub double
i nic więcej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 19:53:56
Dlaczego jest formalnie UB zostało omówione w kilku odpowiedziach.
Powód, dla którego masz konkretnie to zachowanie, jest zależny od platformy, ale prawdopodobnie jest następujący:
-
printf
oczekuje jego argumentów zgodnie ze standardową propagacją vararg. Oznacza to, że {[1] } będziedouble
i wszystko mniejsze od {[3] } będzieint
. - przekazujesz
int
gdzie funkcja oczekujedouble
. Twójint
jest prawdopodobnie 32-bitowy, Twójdouble
64-bitowy. Że oznacza to, że cztery bajty stosu zaczynające się w miejscu, w którym ma się znajdować argument to0
, ale następujące cztery bajty mają dowolną zawartość. To jest to, co jest używane do konstruowania wartości, która jest wyświetlana.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-27 07:14:33
Główna przyczyna tego problemu "nieokreślonej wartości" znajduje się w obsadzie wskaźnika na int
wartość przekazana do sekcji printf
parametry zmiennej do wskaźnika na typach double
, które wykonuje makro va_arg
.
Powoduje to odwołanie się do obszaru pamięci, który nie został całkowicie zainicjowany z wartością przekazaną jako parametr do printf, ponieważ double
Rozmiar obszaru bufora pamięci jest większy niż int
Rozmiar.
Dlatego, gdy ten wskaźnik jest dereferenced, jest zwracany nieokreślona wartość, lub lepiej "wartość", która zawiera w części wartość przekazaną jako parametr do printf
, A dla pozostałej części może pochodzić z innego obszaru bufora stosu lub nawet obszaru kodu (wywołanie wyjątku błędu pamięci), rzeczywiste przepełnienie bufora.
może brać pod uwagę te specyficzne fragmenty semplifikowanych implementacji kodu "printf" i "va_arg"...
printf
va_list arg;
....
case('%f')
va_arg ( arg, double ); //va_arg is a macro, and so you can pass it the "type" that will be used for casting the int pointer argument of printf..
....
the real implementacja w vprintf (biorąc pod uwagę gnu impl.) o podwójnych parametrach wartości zarządzanie jest:
if (__ldbl_is_dbl) { args_value[cnt].pa_double = va_arg (ap_save, double); ... }
va_arg
char *p = (double *) &arg + sizeof arg; //printf parameters area pointer
double i2 = *((double *)p); //casting to double because va_arg(arg, double)
p += sizeof (double);
linki
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:24:19