631 Stimmen

Wann ist reinterpret_cast zu verwenden?

Ich bin etwas verwirrt über die Anwendbarkeit von reinterpret_caststatic_cast . Nach dem, was ich gelesen habe, sind die allgemeinen Regeln statische Besetzung zu verwenden, wenn die Typen zur Kompilierzeit interpretiert werden können, daher das Wort static . Dies ist der Cast, den der C++-Compiler intern auch für implizite Casts verwendet.

reinterpret_cast s sind in zwei Szenarien anwendbar:

  • Ganzzahlige Typen in Zeigertypen umwandeln und umgekehrt
  • einen Zeigertyp in einen anderen umwandeln. Die allgemeine Vorstellung, die ich bekomme, ist, dass dies unportabel ist und vermieden werden sollte.

Wo ich ein wenig verwirrt bin, ist eine Verwendung, die ich benötige, ich rufe C++ von C und der C-Code muss auf das C++-Objekt halten, so dass im Grunde hält es eine void* . Welcher Cast sollte verwendet werden, um zwischen den void * und den Klassentyp?

Ich habe die Verwendung von beidem gesehen static_cast y reinterpret_cast ? Doch nach dem, was ich gelesen habe, scheint es static besser ist, da der Cast zur Kompilierzeit erfolgen kann? Obwohl es heißt, man solle reinterpret_cast um von einem Zeigertyp in einen anderen umzuwandeln?

30 Stimmen

reinterpret_cast findet nicht zur Laufzeit statt. Beides sind Anweisungen zur Kompilierzeit. Von de.cppreference.com/w/cpp/language/reinterpret_cast : "Im Gegensatz zu static_cast, aber wie const_cast, wird der Ausdruck reinterpret_cast nicht in CPU-Anweisungen kompiliert. Es handelt sich um eine reine Compilerdirektive, die den Compiler anweist, die Bitfolge (Objektdarstellung) des Ausdrucks so zu behandeln, als hätte sie den Typ new_type."

0 Stimmen

@HeretoLearn, ist es möglich, die relevanten Codestücke aus der *.c und *.cpp Datei hinzuzufügen? Ich denke, das kann die Aussagekraft der Frage verbessern.

580voto

jalf Punkte 235501

Der C++-Standard garantiert Folgendes:

static_cast mit einem Zeiger auf und von void* behält die Adresse bei. Das heißt, im Folgenden, a , b y c zeigen alle auf dieselbe Adresse:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_cast garantiert nur, dass, wenn Sie einen Zeiger auf einen anderen Typ casten, _und dann reinterpret_cast zurück zum ursprünglichen Typ_ erhalten Sie den ursprünglichen Wert. Also im Folgenden:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

a y c enthalten den gleichen Wert, aber der Wert von b ist nicht spezifiziert. (in der Praxis wird sie normalerweise dieselbe Adresse enthalten wie a y c aber das ist nicht in der Norm festgelegt und gilt möglicherweise nicht für Maschinen mit komplexeren Speichersystemen).

Für den Guss von und nach void* , static_cast sollte bevorzugt werden.

18 Stimmen

Ich finde es gut, dass 'b' nicht definiert ist. Das verhindert, dass man dumme Sachen damit macht. Wenn man etwas in einen anderen Zeigertyp umwandelt, kann man Probleme bekommen, und die Tatsache, dass man sich nicht darauf verlassen kann, macht einen vorsichtiger. Wenn Sie oben static_cast<> verwendet hätten, wozu ist dann 'b' überhaupt gut?

5 Stimmen

Ich dachte, dass reinterpret_cast<> das gleiche Bitmuster garantiert. (was nicht dasselbe ist wie ein gültiger Zeiger auf einen anderen Typ).

1 Stimmen

@Martin - reinterpret_cast<> führt nicht garantiert zum gleichen Bitmuster. "Das Mapping, das von reinterpret_cast<> durchgeführt wird, ist implementierungsdefiniert." (C++03 5.3.10). Der Standard stellt jedoch fest, dass "es nicht überraschend sein soll".

223voto

jwfearn Punkte 27520

Ein Fall, in dem reinterpret_cast ist notwendig, wenn Schnittstellen zu undurchsichtigen Datentypen vorhanden sind. Dies kommt häufig bei APIs von Anbietern vor, über die der Programmierer keine Kontrolle hat. Hier ist ein konstruiertes Beispiel, bei dem ein Anbieter eine API zum Speichern und Abrufen beliebiger globaler Daten bereitstellt:

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

Um diese API zu verwenden, muss der Programmierer seine Daten in VendorGlobalUserData und wieder zurück. static_cast nicht funktionieren wird, muss man reinterpret_cast :

// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;

struct MyUserData {
    MyUserData() : m(42) {}
    int m;
};

int main() {
    MyUserData u;

        // store global data
    VendorGlobalUserData d1;
//  d1 = &u;                                          // compile error
//  d1 = static_cast<VendorGlobalUserData>(&u);       // compile error
    d1 = reinterpret_cast<VendorGlobalUserData>(&u);  // ok
    VendorSetUserData(d1);

        // do other stuff...

        // retrieve global data
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
//  p = d2;                                           // compile error
//  p = static_cast<MyUserData *>(d2);                // compile error
    p = reinterpret_cast<MyUserData *>(d2);           // ok

    if (p) { cout << p->m << endl; }
    return 0;
}

Nachfolgend finden Sie eine erfundene Implementierung der Beispiel-API:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }

11 Stimmen

Ja, das ist so ziemlich die einzige sinnvolle Verwendung von reinterpret_cast, die mir einfällt.

13 Stimmen

Die Frage kommt vielleicht etwas spät, aber warum verwendet die Hersteller-API nicht void* dafür?

29 Stimmen

@Xeo Sie verwenden keine void *, weil sie dann (einige) Typüberprüfungen zur Kompilierzeit verlieren.

207voto

Mariusz Jaskółka Punkte 3627

Die kurze Antwort: Wenn Sie nicht wissen, was reinterpret_cast steht, verwenden Sie es nicht. Wenn Sie es in Zukunft brauchen, werden Sie es wissen.

Vollständige Antwort:

Betrachten wir die grundlegenden Zahlentypen.

Wenn Sie zum Beispiel konvertieren int(12) à unsigned float (12.0f) Ihr Prozessor muss einige Berechnungen durchführen, da beide Zahlen eine unterschiedliche Bitdarstellung haben. Das ist es, was static_cast steht für.

Andererseits, wenn Sie die reinterpret_cast die CPU ruft keine Berechnungen auf. Sie behandelt einen Satz von Bits im Speicher einfach so, als ob er einen anderen Typ hätte. Wenn Sie also konvertieren int* à float* mit diesem Schlüsselwort hat der neue Wert (nach der Zeigerdereferenzierung) nichts mit dem alten Wert im mathematischen Sinne zu tun.

Ejemplo: Es stimmt, dass reinterpret_cast ist aus einem Grund nicht portabel - die Byte-Reihenfolge (Endianness). Aber das ist oft überraschenderweise der beste Grund, sie zu verwenden. Stellen wir uns ein Beispiel vor: Sie müssen eine binäre 32-Bit-Zahl aus einer Datei lesen und wissen, dass sie Big Endian ist. Ihr Code muss generisch sein und sowohl auf Big-Endian- (z. B. einige ARM) als auch auf Little-Endian-Systemen (z. B. x86) korrekt funktionieren. Sie müssen also die Byte-Reihenfolge überprüfen. Es ist zur Kompilierzeit bekannt, so dass Sie schreiben können constexpr Funktion: Sie können eine Funktion schreiben, um dies zu erreichen:

/*constexpr*/ bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}

Erläuterung: die binäre Darstellung von x im Speicher könnte sein 0000'0000'0000'0001 (groß) oder 0000'0001'0000'0000 (Little Endian). Nach der Neuinterpretation wird das Byte unter p Zeiger könnte jeweils sein 0000'0000 ou 0000'0001 . Wenn Sie statisches Casting verwenden, wird es immer 0000'0001 unabhängig davon, welche Endianness verwendet wird.

EDITAR:

<em>In der ersten Version habe ich die Beispielfunktion <code>is_little_endian</code> zu sein <code>constexpr</code> . Es kompiliert gut auf dem neuesten gcc (8.3.0), aber der Standard sagt, es ist illegal. Der Clang-Compiler weigert sich, es zu kompilieren (was korrekt ist).</em>

5 Stimmen

Schönes Beispiel! Ich würde short für uint16_t und unsigned char für uint8_t ersetzen, um es für Menschen weniger undurchsichtig zu machen.

3 Stimmen

@JanTuron stimmt, wir können nicht davon ausgehen, dass short benötigt 16 Bits im Speicher. Korrigiert.

5 Stimmen

Das Beispiel ist falsch. reinterpret_cast ist in constexpr-Funktionen nicht erlaubt

21voto

flodin Punkte 5116

Die Bedeutung von reinterpret_cast ist nicht durch den C++-Standard definiert. Daher kann theoretisch ein reinterpret_cast könnte Ihr Programm zum Absturz bringen. In der Praxis versuchen die Compiler, das zu tun, was Sie erwarten, nämlich die Bits der übergebenen Daten so zu interpretieren, als wären sie der Typ, auf den Sie casten. Wenn Sie wissen, was die Compiler, die Sie verwenden werden, mit reinterpret_cast Sie können es verwenden, aber zu sagen, es sei tragbar wäre eine Lüge.

In dem von Ihnen beschriebenen Fall und in so ziemlich jedem anderen Fall, in dem Sie Folgendes in Betracht ziehen könnten reinterpret_cast können Sie verwenden static_cast oder eine andere Alternative zu wählen. In der Norm heißt es unter anderem, was Sie erwarten können von static_cast (§5.2.9):

Ein rvalue vom Typ "pointer to cv void" kann explizit in einen Zeiger auf einen Objekttyp umgewandelt werden. Ein Wert vom Typ "pointer to object", der in "pointer to cv void" und zurück in den ursprünglichen Zeigertyp konvertiert wird, hat seinen ursprünglichen Wert.

Für Ihren Anwendungsfall scheint es also ziemlich klar zu sein, dass das Standardisierungskomitee beabsichtigt, dass Sie die static_cast .

7 Stimmen

Ihr Programm stürzt nicht ganz ab. Der Standard bietet ein paar Garantien für reinterpret_cast. Nur nicht so viele, wie die Leute oft erwarten.

1 Stimmen

Nun, reinterpret_cast selbst würde wahrscheinlich nicht abstürzen, aber es könnte ein falsches Ergebnis zurückgeben, das, wenn Sie versuchen, es zu verwenden, einen Absturz verursachen könnte.

2 Stimmen

Nicht, wenn Sie sie richtig einsetzen. Das heißt, reinterpret_cast von A nach B nach A ist vollkommen sicher und wohldefiniert. Aber der Wert von B ist nicht spezifiziert, und ja, wenn man sich darauf verlässt, können schlimme Dinge passieren. Aber der Cast selbst ist sicher genug, solange man ihn nur so verwendet, wie es der Standard erlaubt ;)

11voto

Adam P. Goucher Punkte 249

Eine Verwendung von reinterpret_cast ist, wenn Sie bitweise Operationen auf (IEEE 754) Floats anwenden wollen. Ein Beispiel hierfür ist der Trick der schnellen inversen Quadratwurzel:

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

Sie behandelt die binäre Darstellung der Gleitkommazahl als Ganzzahl, verschiebt sie nach rechts und subtrahiert sie von einer Konstanten, wodurch der Exponent halbiert und negiert wird. Nach der Rückkonvertierung in eine Fließkommazahl wird eine Newton-Raphson-Iteration durchgeführt, um diese Annäherung genauer zu machen:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the deuce? 
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

Dies wurde ursprünglich in C geschrieben, so verwendet C casts, aber die analoge C++ cast ist die reinterpret_cast.

1 Stimmen

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/6S4ijc

4 Stimmen

Der Standard sagt, dass dies ein undefiniertes Verhalten ist: de.cppreference.com/w/cpp/language/reinterpret_cast (unter "Typ-Aliasing")

0 Stimmen

@CrisLuengo Wenn ich alle ersetzen reinterpret_cast con memcpy ist es immer noch UB?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X