static_cast<char*>(static_cast<void*>(&pod)) vs. reinterpret_cast<char*>(&pod)



  • hustbaer schrieb:

    Ist eigentlich definiert dass bei reinterpret_cast<char*>(&pod) das selbe rauskommen muss wie bei static_cast<char*>(static_cast<void*>(&pod)) ?

    Ich weiß nicht. Kannst Du aber trotzdem sorgenlos benutzen. Wenn ich mich richtig erinnere, gab's dazu mal eine Diskussion in comp.lang.c++ oder comp.lang.c++.moderated und der Konsens war, dass das mit dem reinterpret_cast okay ist und dasselbe rauskommen müsste. Ich kann mir nicht vorstellen, was da schiefgehen kann.

    Bei einem static_cast<void*>(&pod) wird meines Wissens nach explizit gesagt, dass ein Zeiger herauskommt, der auf den "Anfang" des Objekts zeigt. Warum das bei reinterpret_cast<char*>(&pod) bei einer noch so "komischen" Implementierung nicht gelten soll, leuchtet mir nicht ein.

    Alternativ kann man (und sollte vielleicht auch) sich die Bytes per Hand zusammenbauen. Wenn es zB um das Lesen/Schreiben von Binärdaten geht, ist es so am einfachsten, ein plattformunabhängiges Format/Protokoll zu verwenden. Dazu verwende ich allerdings unsigned char und reinterpret_cast -e zwischen char* und unsigned char*.



  • Janjan schrieb:

    Na und? Du hast trotzdem nicht auf meine Frage reagiert.

    Wieso sollte ich auf deine Frage reagieren wenn sie komplett am Thema vorbei geht?
    Ja, toll, machen wir einen Template-Overload.
    Bleibt immer noch die Frage wie ich aus dem T* im Template einen char* bekomme. 🙄



  • @krümelkacker:
    Ja, das sind zwei verschiedene Probleme. Einmal wie man den Zeiger "richtig" castet, und dann wie man etwas plattformunabhängig in einen Byte-Stream serialisieren kann bzw. daraus de-serialisieren.

    Mir geht es hier mal nur um das Casten des Zeigers. Und auch nicht darum was funktioniert (ich weiss dass beides funktioniert), sondern darum was der Standard sagt.


  • Administrator

    Also, ich habe mich damit mal auseinander gesetzt. Ich weiss nicht, ob ich wirklich bereits alles gefunden habe, aber vielleicht mal einen Ansatz.

    Schauen wir uns zuerst static_cast<char*>(static_cast<void*>(&pod)) an. Nehmen wir das ganze mal in die einzelnen Schritte auseinander:

    POD* pp = &pod;                   // (1)
    void* p = static_cast<void*>(pp); // (2)
    char* c = static_cast<char*>(p);  // (3)
    

    Nach 5.2.9 Absatz 2 können wir den Schritt (2) folgendermassen umschreiben:

    POD* pp = &pod;                   // (1)
    void* p(pp);                      // (2)
    char* c = static_cast<char*>(p);  // (3)
    

    Gut. Schritt (1) ergibt einen Zeiger auf POD (5.3.1 Absatz 2). Ein Zeiger auf POD kann man in einen Zeiger auf void umwandeln und dabei zeigt der Zeiger auf void auf den Anfang des Speicherbereichs von POD (4.10 Absatz 2). Somit wäre auch Schritt (2) definiert. Schritt (3) wird interessant. Nach 5.2.9 Absatz 10 kann man einen Zeiger auf void per static_cast in einen Zeiger auf char umwandeln, sofern keine cv-Qualifizierung verloren geht. Das ist hier ja gegeben. Zudem steht in 3.9.2 Absatz 4, dass ein Zeiger auf char und ein Zeiger auf void die gleiche Repräsentation und Alignment haben. Somit nehme ich an, wenn ich das alles korrekt verstanden habe, dass c im Schritt (3) wirklich auf das erste Byte von pod zeigt.

    Und nun kommt die Frage, wie es denn jetzt bei reinterpret_cast aussieht? Was passiert da? Wenn man das Kapitel 5.2.10 liest, dann steht da ziemlich oft "implementation defined" oder "unspecified". Dieses Kapitel beschreibt das Verhalten von reinterpret_cast .

    So wie ich dies lese, verstehe und interpretiere, gibt es keine Garantie vom Standard, dass die beiden Konvertierungen gleich sind. Vor allem weil die Konvertierung per reinterpret_cast schlicht und einfach "implementation defined" zu sein scheint.

    Ich hoffe, dass dies jemand anderes nochmals prüft. Vielleicht findet camper etwas Zeit dafür 🙂

    Grüssli



  • Es wird immer auf das erste Byte gezeigt, wenn man die Referenz einfach umcastet!
    Es wird einfach nur das Bytemuster "anders" interpretiert.

    Angenommen "pod" befindet sich an Addresse 0xDEADBEEF, dann hat der Zeiger c nach "char* c = reinterpret_cast<char*>(&pod);" auch 0xDEADBEEF in sich stehen.

    Das ganze rumgetue mit static_casts finde ich hier unnötig, der Compiler bietet euch doch die Brechkeule namens reinterpret_cast an 😉


  • Administrator

    @Icematrix,
    Kannst du das auch beweisen? Womöglich mit dem C++ Standard? Kapitel, Absatz? Oder ist es einfach nur deine Vermutung?

    Grüssli



  • ISO/IEC 14882:2003 5.2.10 §7 schrieb:

    A pointer to an object can be explicitly converted to a pointer to an object of different type.

    Except that converting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type
    yields the original pointer value, the result of such a pointer conversion is unspecified.

    Es geht also, es sei denn, T1 ist durch das Alignment nicht auf T2 abbildbar. Das gleiche Problem gibt es auch beim Umweg über void* (und zwar an der Stelle, an der void void* auf T* gecastet wird).
    Die Rückkonvertierung ist ebenfalls nicht spezifiziert, da POD* höchstwahrscheinlich strengere Alignment-Anforderungen hat als char*
    Passt also.



  • @Icematrix,
    Kannst du das auch beweisen? Womöglich mit dem C++ Standard? Kapitel, Absatz? Oder ist es einfach nur deine Vermutung?

    Grüssli

    Reine Logik, ich habe mehr mit dem Kompiliat eines C++-Compilers zu tun, also dem ASM-Code, als mit normalem C++-Code.

    Ansonsten, das steht in jedem Lehrbuch, dass der reinterpret_cast einfach nur das Bytemuster neu interpretiert, um den Compiler zu Sachen zu zwingen, die seine Typsicherheit verbietet.

    Im Endeffekt ist es total egal, ob eine Addresse in einem unsigned long oder in einem char* oder einem FooBarClass& ist, für den Compiler sind das alles nur vorzeichenlose Integer, die groß genug sind, eine Speicheraddresse zu halten.



  • Icematix schrieb:

    [...]

    Wenn man den Zeiger nach dem Cast noch benutzen will, ist das so falsch.



  • Wenn man den Zeiger nach dem Cast noch benutzen will, ist das so falsch.

    Möchtest du genauer erläutern, wie du das meinst?
    Solange du auf ASM-Ebene "legal" bleibst, kannst du den C++-Compiler zu den ekligsten Sachen zwingen 😉


  • Administrator

    Tachyon schrieb:

    Es geht also, es sei denn, T1 ist durch das Alignment nicht auf T2 abbildbar.

    Wie du aus dem Abschnitt schliesst, dass es gehen soll, ist mir rätselhaft. Kannst du das mal näher erläutern und vielleicht übersetzen, wie du dies verstehst?

    Tachyon schrieb:

    Das gleiche Problem gibt es auch beim Umweg über void* (und zwar an der Stelle, an der void void* auf T* gecastet wird).

    Nein, da gibt es keine Probleme. Beim Cast von void* auf T* ist T char . Und dafür gibt es eben: 3.9.2 Absatz 4.

    @Icematix,
    Es geht hier aber darum, was der Standard sagt, und nicht was ein Kompiler liefert oder ein Lehrbuch sagt. Hustbaer interessiert sich für die Aussage dazu vom Standard:

    hustbaer schrieb:

    Mir geht es hier mal nur um das Casten des Zeigers. Und auch nicht darum was funktioniert (ich weiss dass beides funktioniert), sondern darum was der Standard sagt.

    Grüssli



  • Wegen Falschaussage selbst entfernt.



  • Diskussionen darüber was alles geht und was übliche Compiler wie machen interessieren mich hier gar nicht. Das weiss ich nämlich alles hübsch selbst.

    Mich interessiert was der Standard vorschreibt.

    Ich hätte mir auch gedacht dass das klar aus meinem Beitrag hervorgeht. Demzufolge halte ich einige Beiträge hier auch für ziemlich unpassend.

    Tachyon schrieb:

    ISO/IEC 14882:2003 5.2.10 §7 schrieb:

    A pointer to an object can be explicitly converted to a pointer to an object of different type.

    Except that converting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type
    yields the original pointer value, the result of such a pointer conversion is unspecified.

    Es geht also, es sei denn, T1 ist durch das Alignment nicht auf T2 abbildbar. Das gleiche Problem gibt es auch beim Umweg über void* (und zwar an der Stelle, an der void void* auf T* gecastet wird).
    Die Rückkonvertierung ist ebenfalls nicht spezifiziert, da POD* höchstwahrscheinlich strengere Alignment-Anforderungen hat als char*
    Passt also.

    In dem Absatz steht ganz was anderes. Lies ihn noch ein paar mal, vielleicht wird's dann klar.
    Da steht dass das Ergebnis der Konvertierung von T1 nach T2 "unspecified" ist.(Die Konvertierung selbst ist erlaubt, aber der Standard definiert hier nicht wie das Ergebnis auszusehen hat)

    Lediglich wenn man wieder zurück-konvertiert, also T1 -> T2 -> T1, dann muss am Ende wieder das rauskommen mit dem man angefangen hat. Vorausgesetzt dass T2 keine "strengeren" Alignment-Vorgaben hat als T1.

    Also genau andersrum. BTW: speziell dass man von void* wieder zu T zurückcasten darf, wenn der void* aus einem T* "gewonnen" wurde, sollte auch hinlänglich bekannt sein.

    @Icematix:
    Auf Plattformen mit "flacher" Adressierung sind die meisten Dinge recht einfach, ja.
    Der Standard erlaubt allerdings durchaus Plattformen wo Zeiger auf verschiedene Typen unterschiedlich gross sind. Oder einfach nur unterschiedlich zusammengesetzt.
    Dort könnte ein reinterpret_cast dann gar nicht als "Bitmuster anders interpretieren" implementiert sein, da das Bitmuster in der Länge nicht passt.
    Tjo.



  • hustbaer schrieb:

    [...]

    Du hast recht. Asche über mein Haupt. Irgendwie hat mich der eingeschobene Klammersetz durcheinander gebracht.
    Es ist erlaubt, aber unspezifiziert... Ich hasse den Standard.



  • @Icematix:
    Auf Plattformen mit "flacher" Adressierung sind die meisten Dinge recht einfach, ja.
    Der Standard erlaubt allerdings durchaus Plattformen wo Zeiger auf verschiedene Typen unterschiedlich gross sind. Oder einfach nur unterschiedlich zusammengesetzt.
    Dort könnte ein reinterpret_cast dann gar nicht als "Bitmuster anders interpretieren" implementiert sein, da das Bitmuster in der Länge nicht passt.
    Tjo.

    Dann benutz dafür den static_cast, wenn du eine intelligente Umwandlung brauchst?

    Der reinterpret_cast macht seinen Dienst, wird dir eben nicht das korrekte Ergebnis liefern.
    So würde zb auf meiner x86-Kiste

    unsigned int32 var32 = 0xABCDEF00;
    unsigned int16 var16 = reinterpret_cast<unsigned int16>(var32);
    

    var16 0x00EF enthalten während sie bei einem static_cast overflowen würde.



  • Tachyon schrieb:

    hustbaer schrieb:

    [...]

    Du hast recht. Asche über mein Haupt. Irgendwie hat mich der eingeschobene Klammersetz durcheinander gebracht.
    Es ist erlaubt, aber unspezifiziert... Ich hasse den Standard.

    Naja, der Absatz ist auch eine Glanzleistung an "unverständlich formuliert". Ich hab den Satz auch mehr als 2x lesen müssen um zu verstehen was das jetzt heissen soll. (Zumindest glaube ich dass ich es richtig verstanden habe :D)


  • Mod

    Icematix schrieb:

    Der reinterpret_cast macht seinen Dienst, wird dir eben nicht das korrekte Ergebnis liefern.
    So würde zb auf meiner x86-Kiste

    unsigned int32 var32 = 0xABCDEF00;
    unsigned int16 var16 = reinterpret_cast<unsigned int16>(var32);
    

    var16 0x00EF enthalten während sie bei einem static_cast overflowen würde.

    Also mein Compiler weigert sich, das überhaupt zu kompilieren.

    Ein Blick in Defining reinterpret_cast for pointer types kann zur Klarstellung dienen.
    Im Übrigen bin ich der Ansicht, dass diese Änderung nicht notwendig ist, denn diese Klarstellung ist im Grunde schon in 5.2.10/10 angelegt (abgesehen vom Fall, das von oder zu cv void oder Nullpointer gecastet werden).

    An lvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. That is, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type. No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called.67)

    In allen Fällen in denen wir einen denen wir einen dereferenzierbaren Zeiger haben, bestimmt dieser Abschnitt das Ergebnis sowohl eines Refernmz- als auch eine Pointercasts.


Anmelden zum Antworten