Jak już zauważyli inni, rzutowanie wskaźnika na typ inny niż char na wskaźnik na inny typ inny niż char, a następnie dereferencja jest niezdefiniowanym zachowaniem.
To, że printf("%08lx\n", *(unsigned long *)&fValue)
wywołuje niezdefiniowane zachowanie, niekoniecznie musi oznaczać, że uruchomienie programu, który próbuje wykonać taką parodię, spowoduje wymazanie dysku twardego lub spowoduje wyrzucenie demonów nosowych z nosa (dwóch cech niezdefiniowanego zachowania). Na komputerze, na którym oba typy mają te same wymagania dotyczące wyrównania, prawie na pewno zrobi to, czego się spodziewa, czyli wydrukuje reprezentację heksadecymalną wartości zmiennoprzecinkowej, o której mowa.
To nie powinno być zaskakujące. Norma C otwarcie zachęca do implementacji w celu rozszerzenia języka. Wiele z tych rozszerzeń znajduje się w obszarach, które są, ściśle rzecz biorąc, niezdefiniowanym zachowaniem. Na przykład funkcja POSIX dlsym zwraca wartość void*
, ale ta funkcja jest zwykle używana do wyszukiwania adresu funkcji zamiast zmiennej globalnej. Oznacza to, że wskaźnik void zwrócony przez dlsym
musi zostać przesłany do wskaźnika funkcji, a następnie odwołany do wywołania funkcji. Jest to oczywiście niezdefiniowane zachowanie, ale mimo to działa na dowolnej platformie zgodnej z POSIX. Nie zadziała to na architekturze Harvardu, której wskaźniki mają różne rozmiary niż wskaźniki do danych.
Podobnie, rzutowanie wskaźnika na float
na wskaźnik na niepodpisaną liczbę całkowitą, a następnie dereferencja działa na prawie każdym komputerze z prawie każdym kompilatorem, w którym wymagania dotyczące rozmiaru i wyrównania tej niepodpisanej liczby całkowitej są takie same jak w przypadku a float
.
To powiedziawszy, użycie unsigned long
może spowodować problemy. Na moim komputerze unsigned long
ma długość 64 bitów i wymaga 64-bitowego wyrównania. To nie jest kompatybilne z floatem. Byłoby lepiej użyć uint32_t
- na moim komputerze, to znaczy.
Związek Hack jest jednym ze sposobów obejścia tego bałaganu:
typedef struct {
float fval;
uint32_t ival;
} float_uint32_t;
Przypisanie do float_uint32_t.fval
i dostępu od A `float_uint32_t.ival` kiedyś niezdefiniowane zachowanie. Tak już nie jest w przypadku C. Żaden znany mi kompilator nie wysadza nosowych demonów dla hackowania związków. To nie było UB w C++. To było nielegalne. Do czasu C++ 11, zgodny kompilator C++ musiał narzekać, że jest zgodny.
Każda nawet lepszy sposób wokół tego bałaganu jest użycie formatu na %a
została częścią standardu C od 1999:
printf ("%a\n", fValue);
Jest to prosty, łatwy, przenośny, a tam nie ma szansa na niezdefiniowane zachowanie. To drukuje szesnastkową/binarną reprezentację wartości zmiennoprzecinkowej podwójnej precyzji, o której mowa. Ponieważ printf
jest funkcją archaiczną, wszystkie argumenty float
są konwertowane na double
przed wywołaniem printf
. Ta konwersja musi być dokładna zgodnie z wersją standardu C z 1999 roku. Można odebrać tę dokładną wartość, dzwoniąc pod numer scanf
lub jej siostry.
To jest niezdefiniowane zachowanie. Jest to coś, co ludzie robili zanim C został ujednolicony w 1989 roku, a niektórzy nie nadążali z czasem –