Jestem trochę mylić z stosowalności reinterpret_cast
vs static_cast
. Z tego, co przeczytałem, ogólne zasady używają rzutowania statycznego, gdy typy mogą być interpretowane w czasie kompilacji, stąd słowo static
. Jest to rzutowanie, którego kompilator C ++ używa wewnętrznie do rzutowania niejawnego.
reinterpret_cast
mają zastosowanie w dwóch scenariuszach:
- konwertuj typy liczb całkowitych na typy wskaźników i odwrotnie
- przekonwertować jeden typ wskaźnika na inny. Ogólny pomysł, jaki mam, jest taki, że nie można go przenosić i należy go unikać.
Tam, gdzie jestem trochę zdezorientowany, potrzebuję jednego użycia, wywołuję C ++ z C, a kod C musi trzymać się obiektu C ++, więc w zasadzie zawiera on void*
. Jakiej obsady należy użyć do konwersji między void *
typem a klasą?
Widziałem użycie obu static_cast
i reinterpret_cast
? Chociaż z tego, co czytałem, wydaje się static
to lepsze, ponieważ obsada może się zdarzyć w czasie kompilacji? Chociaż mówi się, aby użyć reinterpret_cast
do konwersji z jednego typu wskaźnika na inny?
reinterpret_cast
nie dzieje się w czasie wykonywania. Oba są instrukcjami kompilacji. From en.cppreference.com/w/cpp/language/reinterpret_cast : "W przeciwieństwie do static_cast, ale podobnie jak const_cast, wyrażenie reinterpret_cast nie kompiluje się z żadnymi instrukcjami procesora. Jest to wyłącznie dyrektywa kompilatora, która instruuje kompilator, aby traktował sekwencję bitów (reprezentacja obiektu) wyrażenia, jakby miało typ nowy_typ. "Odpowiedzi:
Standard C ++ gwarantuje:
static_cast
wprowadzenie wskaźnika do i zvoid*
zachowuje adres. Oznacza to, że w dalszej częścia
,b
ac
wszystko wskazuje na ten sam adres:reinterpret_cast
gwarantuje tylko, że jeśli rzutujesz wskaźnik na inny typ, a następnie zreinterpret_cast
powrotem na typ oryginalny , otrzymujesz oryginalną wartość. Więc w następujący sposób:a
ic
zawierają tę samą wartość, ale wartość nieb
jest określona. (w praktyce zwykle będzie zawierał ten sam adres coa
ic
, ale nie jest to określone w standardzie, i może nie być prawdziwe na komputerach z bardziej złożonymi systemami pamięci).Do castowania do iz
void*
,static_cast
powinno być preferowane.źródło
b
nie jest już nieokreślona w C ++ 11 podczas używaniareinterpret_cast
. A w C ++ 03 zabroniono wykonywania rzutowaniaint*
na (chociaż kompilatory tego nie implementowały i było to niepraktyczne, dlatego zmieniono na C ++ 11).void*
reinterpret_cast
Jednym z przypadków, w których
reinterpret_cast
jest to konieczne, jest połączenie z nieprzezroczystymi typami danych. Zdarza się to często w interfejsach API dostawców, nad którymi programista nie ma kontroli. Oto wymyślony przykład, w którym dostawca udostępnia interfejs API do przechowywania i wyszukiwania dowolnych danych globalnych:Aby użyć tego interfejsu API, programista musi przesyłać swoje dane do
VendorGlobalUserData
iz powrotem.static_cast
nie będzie działać, należy użyćreinterpret_cast
:Poniżej znajduje się przemyślana implementacja przykładowego interfejsu API:
źródło
void*
?USpoofChecker*
, gdzieUSpoofChecker
jest pustą strukturą. Jednak pod maską, za każdym razem, gdy przechodziszUSpoofChecker*
, przechodzireinterpret_cast
do wewnętrznego typu C ++.Krótka odpowiedź: jeśli nie wiesz co
reinterpret_cast
oznacza skrót, nie używaj go. Jeśli będziesz go potrzebować w przyszłości, będziesz wiedział.Pełna odpowiedź:
Rozważmy podstawowe typy liczb.
Podczas konwersji na przykład
int(12)
naunsigned float (12.0f)
procesor należy wykonać pewne obliczenia, ponieważ obie liczby mają różne odwzorowanie bitów. To właśniestatic_cast
oznacza.Z drugiej strony, podczas wywoływania
reinterpret_cast
procesor nie wywołuje żadnych obliczeń. Po prostu traktuje zestaw bitów w pamięci tak, jakby miał inny typ. Więc po konwersjiint*
dofloat*
z tego słowa kluczowego, nowa wartość (po wskaźnik dereferecing) nie ma nic wspólnego ze starą wartość w matematycznym znaczeniu.Przykład: Prawdą jest, że
reinterpret_cast
nie jest przenośny z jednego powodu - kolejności bajtów (endianness). Ale często jest to zaskakująco najlepszy powód do korzystania z niego. Wyobraźmy sobie przykład: musisz odczytać binarną 32-bitową liczbę z pliku i wiesz, że to duży endian. Twój kod musi być ogólny i działa poprawnie na systemach big endian (np. Niektóre ARM) i little endian (np. X86). Musisz więc sprawdzić kolejność bajtów.Jest dobrze znany z czasu kompilacji, więc możesz napisaćMożesz napisać funkcję, aby to osiągnąć:constexpr
funkcję:Objaśnienie: binarna reprezentacja
x
w pamięci może być0000'0000'0000'0001
(duża) lub0000'0001'0000'0000
(mała endian). Po reinterpretacji rzutowania bajtem podp
wskaźnikiem może być odpowiednio0000'0000
lub0000'0001
. Jeśli użyjesz castingu statycznego, zawsze tak będzie0000'0001
, bez względu na to, jaki endianizm jest używany.EDYTOWAĆ:
W pierwszej wersji zrobiłem przykładową funkcję
is_little_endian
byciaconstexpr
. Kompiluje się dobrze na najnowszym gcc (8.3.0), ale standard mówi, że jest nielegalny. Kompilator clang nie chce go skompilować (co jest poprawne).źródło
short
zajmuje 16 bitów pamięci. PoprawioneZnaczenie
reinterpret_cast
nie jest zdefiniowane przez standard C ++. Dlatego teoretyczniereinterpret_cast
może to spowodować awarię programu. W praktyce kompilatory starają się robić to, czego oczekujesz, czyli interpretować bity tego, co przekazujesz, tak jakby były typem, na który rzucasz. Jeśli wiesz, jakie kompilatory zamierzasz używać,reinterpret_cast
możesz go użyć, ale powiedzieć, że jest przenośny , kłamałoby.W przypadku, który opisujesz, a właściwie w każdym przypadku, który możesz rozważyć
reinterpret_cast
, możeszstatic_cast
zamiast tego użyć innej alternatywy. Między innymi standard ma to do powiedzenia na temat tego, czego możesz oczekiwaćstatic_cast
(§5.2.9):Tak więc w twoim przypadku użycia wydaje się dość jasne, że komitet normalizacyjny zamierzał z niego skorzystać
static_cast
.źródło
reinterpret_crash
. W żaden sposób błąd kompilatora nie powstrzyma mnie przed awarią mojego programu reinterpretacji.template<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }
Jednym z zastosowań reinterpret_cast jest użycie operacji bitowych na liczbach zmiennoprzecinkowych (IEEE 754). Jednym z przykładów jest sztuczka Fast Inverse Square-Root:
https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code
Traktuje binarną reprezentację liczby zmiennoprzecinkowej jako liczbę całkowitą, przesuwa ją w prawo i odejmuje od stałej, tym samym zmniejszając o połowę i negując wykładnik potęgi. Po konwersji z powrotem na liczbę zmiennoprzecinkową poddaje się iteracji Newtona-Raphsona w celu dokładniejszego przybliżenia:
Zostało to pierwotnie napisane w C, więc używa rzutowań C, ale analogicznym rzutowaniem C ++ jest rzut_interpretacji.
źródło
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))
- ideone.com/6S4ijcreinterpret_cast
zememcpy
jest to nadal UB?memcpy
zdecydowanie uczyniłoby to legalnym.Możesz użyć reinterprete_cast, aby sprawdzić dziedziczenie w czasie kompilacji.
Spójrz tutaj: Korzystanie z reinterpret_cast do sprawdzania dziedziczenia w czasie kompilacji
źródło
Próbowałem zakończyć i napisałem prostą bezpieczną obsadę za pomocą szablonów. Pamiętaj, że to rozwiązanie nie gwarantuje rzutowania wskaźników na funkcje.
źródło
reinterpret_cast
już w tej sytuacji: „Wskaźnik obiektu można jawnie przekonwertować na wskaźnik obiektu innego typu. [72] Gdy wartośćv
typu wskaźnika obiektu jest konwertowana na typ wskaźnika obiektu„ wskaźnik na cvT
”, wynik jeststatic_cast<cv T*>(static_cast<cv void*>(v))
. ” - N3797.c++2003
standardu mogę nie zauważyć, żereinterpret_cast
robistatic_cast<cv T*>(static_cast<cv void*>(v))
C++03
to byłoC++98
. Mnóstwo projektów używało starego C ++ zamiast przenośnego C. Czasem trzeba dbać o przenośność. Na przykład musisz obsługiwać ten sam kod w systemach Solaris, AIX, HPUX, Windows. Pod względem zależności i przenośności kompilatora jest to trudne. Dobrym przykładem wprowadzenia piekła przenośności jest użyciereinterpret_cast
w swoim kodzieNajpierw masz jakieś dane określonego typu, takie jak int tutaj:
Następnie chcesz uzyskać dostęp do tej samej zmiennej, co inny typ, taki jak float: możesz wybrać pomiędzy
lub
KRÓTKI: oznacza, że ta sama pamięć jest używana jako inny typ. Możesz więc konwertować binarne reprezentacje liczb zmiennoprzecinkowych jako typu int jak wyżej na zmiennoprzecinkowe. 0x80000000 ma na przykład wartość -0 (mantysa i wykładnik są zerowe, ale znak msb ma wartość jeden. Działa to również w przypadku podwójnych i długich podwójnych.
OPTYMALIZACJA: Myślę, że reinterpret_cast byłby zoptymalizowany w wielu kompilatorach, podczas gdy rzutowanie c jest wykonywane przez pointerarytmikę (wartość musi zostać skopiowana do pamięci, ponieważ wskaźniki nie mogą wskazywać rejestrów cpu).
UWAGA: W obu przypadkach przed rzutowaniem należy zapisać rzutowaną wartość w zmiennej! To makro może pomóc:
źródło
reinterpret_cast
formularzuint
tofloat&
jest niezdefiniowane zachowanie.Jednym z powodów
reinterpret_cast
jest to, że klasa podstawowa nie ma vtable, ale klasa pochodna ma. W takim przypadku,static_cast
ireinterpret_cast
spowoduje różne wartości wskaźnika (byłby to nietypowy przypadek wspomniany powyżej przez jalf ). Jako oświadczenie nie stwierdzam, że jest to część standardu, ale implementacja kilku rozpowszechnionych kompilatorów.Jako przykład weź poniższy kod:
Które generuje coś takiego:
We wszystkich kompilatorach, które wypróbowałem (MSVC 2015 i 2017, clang 8.0.0, gcc 9.2, icc 19.0.1 - patrz godbolt dla ostatnich 3 ) wynik
static_cast
różni się od wynikureinterpret_cast
o 2 (4 dla MSVC). Jedynym kompilatorem ostrzegającym przed różnicą był clang, z:Ostatnim zastrzeżeniem jest to, że jeśli klasa podstawowa nie ma elementów danych (np.
int i;
), To clang, gcc i icc zwracają ten sam adresreinterpret_cast
jak dlastatic_cast
, podczas gdy MSVC nadal nie.źródło
Oto wariant programu Avi Ginsburga, który wyraźnie ilustruje właściwość
reinterpret_cast
wspomnianą przez Chrisa Luengo, flodina i cmdLP: kompilator traktuje wskazaną lokalizację pamięci tak, jakby był obiektem nowego typu:Co daje wynik w ten sposób:
Można zauważyć, że obiekt B jest najpierw wbudowany w pamięć jako dane specyficzne dla B, a następnie osadzony obiekt A.
static_cast
Poprawnie zwraca adres wbudowanego obiektu, a wskaźnik stworzony przezstatic_cast
prawidłowo daje wartość pola danych. Wskaźnik generowany przezreinterpret_cast
smakołykib
lokalizację pamięci tak, jakby był zwykłym obiektem A, a więc gdy wskaźnik próbuje uzyskać pole danych, zwraca niektóre dane specyficzne dla B, tak jakby była zawartością tego pola.Jednym z zastosowań
reinterpret_cast
jest konwersja wskaźnika na liczbę całkowitą bez znaku (gdy wskaźniki i liczby całkowite bez znaku mają ten sam rozmiar):int i;
unsigned int u = reinterpret_cast<unsigned int>(&i);
źródło
Szybka odpowiedź: użyj,
static_cast
jeśli się skompiluje, w przeciwnym razie skorzystaj zreinterpret_cast
.źródło
Przeczytaj FAQ ! Przechowywanie danych C ++ w C może być ryzykowne.
W C ++ wskaźnik do obiektu można przekonwertować
void *
bez rzutowania. Ale to nie jest prawda na odwrót. Musiszstatic_cast
odzyskać oryginalny wskaźnik.źródło