Wanneer reinterpret_cast gebruiken?

Ik ben een beetje in de war met de toepasbaarheid van reinterpret_castversus static_cast. Van wat ik heb gelezen, zijn de algemene regels om statische cast te gebruiken wanneer de typen kunnen worden geïnterpreteerd tijdens het compileren, vandaar het woord static. Dit is de cast die de C++-compiler intern ook gebruikt voor impliciete casts.

reinterpret_casts zijn van toepassing in twee scenario’s:

  • converteer integer-types naar pointer-types en vice versa
  • converteer het ene aanwijzertype naar het andere. Het algemene idee dat ik krijg is dat dit niet overdraagbaar is en moet worden vermeden.

Waar ik een beetje in de war ben, is een gebruik dat ik nodig heb, ik bel C++ vanuit C en de C-code moet het C++-object vasthouden, dus in feite heeft het een void*. Welke cast moet worden gebruikt om te converteren tussen de void *en het Class-type?

Ik heb het gebruik van zowel static_castals reinterpret_castgezien? Hoewel van wat ik heb gelezen, lijkt het erop dat staticbeter is omdat de cast tijdens het compileren kan plaatsvinden? Hoewel er staat dat je reinterpret_castmoet gebruiken om van het ene aanwijzertype naar het andere te converteren?


Antwoord 1, autoriteit 100%

De C++-standaard garandeert het volgende:

static_casteen aanwijzer van en naar void*behoudt het adres. Dat wil zeggen dat a, ben callemaal naar hetzelfde adres verwijzen:

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

reinterpret_castgarandeert alleen dat als je een aanwijzer naar een ander type cast, en dan reinterpret_castterug naar het oorspronkelijke type, je de oorspronkelijke waarde. Dus in het volgende:

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

aen cbevatten dezelfde waarde, maar de waarde van bis niet gespecificeerd. (in de praktijk zal het meestal hetzelfde adres bevatten als aen c, maar dat is niet gespecificeerd in de standaard, en het is mogelijk niet waar op machines met complexere geheugensystemen .)

Voor casten van en naar void*verdient static_castde voorkeur.


Antwoord 2, autoriteit 35%

Eén geval waarin reinterpret_castnodig is, is bij het gebruik van ondoorzichtige gegevenstypen. Dit komt vaak voor in leveranciers-API’s waarover de programmeur geen controle heeft. Hier is een gekunsteld voorbeeld waarbij een leverancier een API levert voor het opslaan en ophalen van willekeurige globale gegevens:

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

Om deze API te gebruiken, moet de programmeur zijn gegevens casten naar VendorGlobalUserDataen weer terug. static_castwerkt niet, men moet reinterpret_castgebruiken:

// 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;
}

Hieronder staat een gekunstelde implementatie van de voorbeeld-API:

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

Antwoord 3, autoriteit 28%

Het korte antwoord:
Als je niet weet waar reinterpret_castvoor staat, gebruik het dan niet. Als je het in de toekomst nodig hebt, weet je het.

Volledig antwoord:

Laten we eens kijken naar de basisnummertypen.

Als u bijvoorbeeld int(12)converteert naar unsigned float (12.0f), moet uw processor enkele berekeningen uitvoeren omdat beide getallen een verschillende bitweergave hebben. Dit is waar static_castvoor staat.

Aan de andere kant, wanneer u reinterpret_castaanroept, roept de CPU geen berekeningen aan. Het behandelt gewoon een set bits in het geheugen alsof het een ander type had. Dus als je int*converteert naar float*met dit sleutelwoord, heeft de nieuwe waarde (na verwijzing van de aanwijzer) niets te maken met de oude waarde in wiskundige betekenis.

Voorbeeld:Het is waar dat reinterpret_castom één reden niet overdraagbaar is: bytevolgorde (endianness). Maar dit is vaak verrassend de beste reden om het te gebruiken. Laten we ons het voorbeeld voorstellen: u moet een binair 32-bits getal uit een bestand lezen en u weet dat het big endian is. Je code moet generiek zijn en goed werken op big endian (bijvoorbeeld sommige ARM) en little endian (bijvoorbeeld x86) systemen. Je moet dus de bytevolgorde controleren. Het is bekend tijdens het compileren, dus je kunt de functie constexprschrijven:Je kunt een functie schrijven om dit te bereiken:

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

Uitleg:de binaire weergave van xin het geheugen kan 0000'0000'0000'0001zijn ( groot) of 0000'0001'0000'0000(little endian). Na herinterpretatie-casting kan de byte onder de p-aanwijzer respectievelijk 0000'0000of 0000'0001zijn. Als u statische casting gebruikt, is dit altijd 0000'0001, ongeacht welke endianness wordt gebruikt.

BEWERKEN:

In de eerste versie heb ik voorbeeldfunctie is_little_endiangemaakt om constexprte zijn. Het compileert prima op de nieuwste gcc (8.3.0) maar de standaard zegt dat het illegaal is. De clang-compiler weigert het te compileren (wat correct is).


Antwoord 4, autoriteit 4%

De betekenis van reinterpret_castwordt niet gedefinieerd door de C++-standaard. Vandaar dat in theorie een reinterpret_castje programma zou kunnen laten crashen. In de praktijk proberen compilers te doen wat je verwacht, namelijk de stukjes die je doorgeeft interpreteren alsof ze het type zijn waarnaar je cast. Als je weet wat de compilers die je gaat gebruiken met reinterpret_castdoen, kun je het gebruiken, maar om te zeggen dat het draagbaaris, zou liegen.

Voor het geval dat u beschrijft, en vrijwel elk geval waarin u reinterpret_castzou kunnen overwegen, kunt u in plaats daarvan static_castof een ander alternatief gebruiken. De standaard zegt onder meer dit over wat u van static_castkunt verwachten (§5.2.9):

Een r-waarde van het type “pointer naar cv void” kan expliciet worden geconverteerd naar een pointer naar objecttype. Een waarde van het type pointer naar object geconverteerd naar “pointer to cv void” en terug naar het originele pointertype zal zijn oorspronkelijke waarde hebben.

Dus voor jouw gebruik lijkt het vrij duidelijk dat de normalisatiecommissie van plan was dat je static_castzou gebruiken.


Antwoord 5, autoriteit 2%

Een gebruik van reinterpret_cast is als u bitsgewijze bewerkingen wilt toepassen op (IEEE 754) floats. Een voorbeeld hiervan was de Fast Inverse Square-Root-truc:

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

Het behandelt de binaire representatie van de float als een geheel getal, verschuift het naar rechts en trekt het af van een constante, waardoor de exponent wordt gehalveerd en genegeerd. Nadat het weer is omgezet naar een vlotter, wordt het onderworpen aan een Newton-Raphson-iteratie om deze benadering nauwkeuriger te maken:

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;
}

Dit is oorspronkelijk geschreven in C, dus gebruikt C-casts, maar de analoge C++-cast is de reinterpret_cast.


Antwoord 6

U kunt reinterprete_cast gebruiken om de overerving te controleren tijdens het compileren.
Kijk hier:
Reinterpret_cast gebruiken om overerving te controleren tijdens het compileren


Antwoord 7

Hier is een variant van het programma van Avi Ginsburg die duidelijk de eigenschap illustreert van reinterpret_castgenoemd door Chris Luengo, flodin en cmdLP: dat de compiler de aangewezen geheugenlocatie behandelt alsof het een object van het nieuwe type:

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
class A
{
public:
    int i;
};
class B : public A
{
public:
    virtual void f() {}
};
int main()
{
    string s;
    B b;
    b.i = 0;
    A* as = static_cast<A*>(&b);
    A* ar = reinterpret_cast<A*>(&b);
    B* c = reinterpret_cast<B*>(ar);
    cout << "as->i = " << hex << setfill('0')  << as->i << "\n";
    cout << "ar->i = " << ar->i << "\n";
    cout << "b.i   = " << b.i << "\n";
    cout << "c->i  = " << c->i << "\n";
    cout << "\n";
    cout << "&(as->i) = " << &(as->i) << "\n";
    cout << "&(ar->i) = " << &(ar->i) << "\n";
    cout << "&(b.i) = " << &(b.i) << "\n";
    cout << "&(c->i) = " << &(c->i) << "\n";
    cout << "\n";
    cout << "&b = " << &b << "\n";
    cout << "as = " << as << "\n";
    cout << "ar = " << ar << "\n";
    cout << "c  = " << c  << "\n";
    cout << "Press ENTER to exit.\n";
    getline(cin,s);
}

Wat resulteert in uitvoer zoals deze:

as->i = 0
ar->i = 50ee64
b.i   = 0
c->i  = 0
&(as->i) = 00EFF978
&(ar->i) = 00EFF974
&(b.i)   = 00EFF978
&(c->i)  = 00EFF978
&b = 00EFF974
as = 00EFF978
ar = 00EFF974
c  = 00EFF974
Press ENTER to exit.

Het is te zien dat het B-object eerst als B-specifieke gegevens in het geheugen wordt ingebouwd, gevolgd door het ingesloten A-object. De static_castretourneert correct het adres van het ingesloten A-object, en de aanwijzer gemaakt door static_castgeeft correct de waarde van het gegevensveld. De aanwijzer die wordt gegenereerd door reinterpret_castbehandelt de geheugenlocatie van balsof het een gewoon A-object is, en dus wanneer de aanwijzer het gegevensveld probeert te krijgen, retourneert hij een aantal B- specifieke gegevens alsof het de inhoud van dit veld is.

Eén gebruik van reinterpret_castis om een aanwijzer om te zetten in een geheel getal zonder teken (wanneer aanwijzers en gehele getallen zonder teken even groot zijn):

int i;
unsigned int u = reinterpret_cast<unsigned int>(&i);


Antwoord 8

template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

Ik probeerde een conclusie te trekken en schreef een eenvoudige veilige cast met behulp van sjablonen.
Merk op dat deze oplossing niet garandeert dat er aanwijzers op een functie worden geplaatst.


Antwoord 9

Eerst heb je wat gegevens van een specifiek type, zoals int hier:

int x = 0x7fffffff://==nan in binary representation

Dan wil je toegang krijgen tot dezelfde variabele als een ander type zoals float:
U kunt kiezen tussen

float y = reinterpret_cast<float&>(x);
//this could only be used in cpp, looks like a function with template-parameters

of

float y = *(float*)&(x);
//this could be used in c and cpp

KORT: het betekent dat hetzelfde geheugen wordt gebruikt als een ander type. Dus je zou binaire weergaven van drijvers kunnen converteren als intype zoals hierboven om te drijven. 0x80000000 is -0 bijvoorbeeld (de mantissa en exponent zijn null, maar het teken, de MSB, is één. Dit werkt ook voor verdubbelingen en lange doubles.

Optimaliseren: ik denk dat Reinterpret_cast in veel compilers zou worden geoptimaliseerd, terwijl de C-casting wordt gemaakt door pointerarithmetica (de waarde moet naar het geheugen worden gekopieerd, kunnen aanwijzingen niet wijzen op CPU-registers).

Opmerking: in beide gevallen moet u de gegoten waarde in een variabele vóór cast opslaan! Deze macro kan helpen:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })

Antwoord 10

Snel antwoord: gebruik static_castals het compileert, anders resort naar reinterpret_cast.


Antwoord 11

Lees de FAQ ! Holding C++ -gegevens in C kan riskant zijn.

In C++ kan een aanwijzer naar een object worden geconverteerd naar void *zonder eventuele casts. Maar het is niet waar andersom. U hebt een static_castnodig om de originele aanwijzer terug te krijgen.

Other episodes