Problematik von reinterpret_cast
-
@pumuckl,
Aber undefiniert ist dieser Code doch nicht? Es ist genau definiert, wasreinterpret_cast
macht und auch die Zuweisung ist genau definiert. Das Ergebnis ist einfach unter Umständen nicht das, was man erwartet und kann unter gewissen Umständen zu einem undefiniertem Verhalten führen. Aber deshalb würde ich jetzt irgendwie nicht den ganzen Code als undefiniert ansehen ...Grüssli
-
queer_boy schrieb:
pumuckl schrieb:
bei C-casts ists das selbe, die sind weder typsicher noch sonstwas.
..., die allerdings meistens dasselbe tun wie ein gewöhnlicher static_cast und von daher bei down- & upcasts genauso sicher wie static_cast sind.
Naja, ein C-Cast kann alles das auf einmal, was static_cast, reinierpret_cast und const_cast jeweils spezialisiert und teilweise mit Typprüfung können.
-
definiere "alles auf einmal".
die reihenfolge, also wann welcher cast angewendet werden soll, ist klar definiert.das wirklich große manko IMO ist, dass ein C-cast static_cast (bzw. manchmal auch reinterpret_cast) mit const_cast mischen kann. ansonsten ist ein einfaches
int foo (double d) { return (int) d; }
völlig in ordnung (und in diesem fall nicht einmal aus lesbarkeitsgründen problematisch).
-
btw. vielleicht sollten sich alle noch einmal camper im original durchlesen, zur erinnerung ein ausschnitt:
camper schrieb:
... Problematisch ist der folgende Zugriff auf den Speicher, dieser ist bis auf wenige Ausnahmen (3.10/15) immer undefiniert ...
camper hat das zwar nur in klammern, aber um verwirrung zu vermeiden, sollte man das betonen: er spricht nur von casts von zeigern bzw. referenzen (deshalb auch der hinweis auf 5.2.10/7)
schon jemand 3.10/15 durchgelesen?
3.10/15 schrieb:
If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined:
— the dynamic type of the object,
— a cv-qualified version of the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of
the object,
— an aggregate or union type that includes one of the aforementioned types among its members (includ-
ing, recursively, a member of a subaggregate or contained union),
— a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
— a char or unsigned char type.zur verwendung von reinterpret_cast im sinne von 5.2.10/7 lässt sich eigentlich nicht mehr sagen. und die anderen arten, diesen cast zu verwenden, sind sowieso schon implementationsabhängig. das einzige, was noch interessant sein könnte, ist 5.2.10/10.
-
victor schrieb:
das heißt also, dass das ergebnis von
char* charptr = new char[4]; int* intptr = reinterpret_cast<int*>(charptr); *intptr = 0;
undefiniert ist?
und wie sieht das dann bei c-style casts aus?int* intptr = (int*)charptr; *intptr = 0; // auch undefiniert?
Selbstverständlich ist das undefiniert.
Man weiß außerdem nicht, ob der Zeiger korrekt ausgerichtet ist für einen Zugriff als int. Auf einer 32-Bit-Plattform muss der Zeiger in der Regel ein vielfaches von 4 sein, damit der Prozessor nicht mit einem unaligned access aussteigt, auch wenn manche Compiler den Code vielleicht "netterweise" synthetisieren, der einen Speicherwort-übergreifenden Zugriff macht. Das sind aber alles Hintergrundinformationen, mit denen man sich nicht rumschlagen sollte. Das hat im übrigen auch nichts mit Casts zu tun, bzw. nur in sofern, dass sie die Möglichkeit für so einen "bad-aligned" Pointer eröffnen.
Für einen sauberen Wert-Transfer lässt mich ein memcpy benutzen:
int i = 0; memcpy( &i, charptr, sizeof(int));
Dadurch ist sichergestellt, dass i korrekt ausgerichtet ist. Der tatsächliche Inhalt ist aber immernoch undefiniert bzw. zumindest implementierungs- und plattformabhängig.
Um es nochmal zu verdeutlichen, Beispiel:
Speicheradresse Daten 0xa0000000 [000000XX] 0xa0000004 [XXXXXX00]
Hier wäre ein Zeiger auf 0xa0000003 unaligned, wenn man die 4 Byte (XX) lesen will. Der Prozessor müsste zwei Lesezugriffe auf den Speicher machen. Dieser Zeiger ist unaligned. Das soll nicht heißen, dass man jetzt seine Zeiger mit %4 prüft oder sonstwas!
Es soll nur die Problematik ansich verdeutlichen, weil das ganze sonst vielleicht etwas zu abstrakt klingt.
Zu dem hier:
queer_boy schrieb:
pumuckl schrieb:
bei C-casts ists das selbe, die sind weder typsicher noch sonstwas.
..., die allerdings meistens dasselbe tun wie ein gewöhnlicher static_cast und von daher bei down- & upcasts genauso sicher wie static_cast sind.
*hust* Nein, nein und nochmals nein.
C-Casts casten wie ein reinterpret_cast, wenn die Typinformationen unvollständig sind (z.B. nur Forward-Deklarationen). Es gibt keine Warnung oder sonstwas. D.h. wenn ausversehen nur eine Forward-Deklaration auf eine Klasse bekannt ist, wird der C-Cast trotzdem casten und den Pointer zerschießen, während static_cast mit einem Fehler aussteigt.
Das trifft sowohl Up-/Down-/Cross-Casts zu. Insbesondere der erste Fall (Up-Cast) ist subtil, aber trotzdem gefährlich. Insbesondere wenn man sogar in "Lehrbüchern" liest, der Zeiger würde bei einem Up-Cast immer gleich bleiben bei Einfachvererbung.
-
7H3 N4C3R schrieb:
C-Casts casten wie ein reinterpret_cast, wenn die Typinformationen unvollständig sind (z.B. nur Forward-Deklarationen). Es gibt keine Warnung oder sonstwas. D.h. wenn ausversehen nur eine Forward-Deklaration auf eine Klasse bekannt ist, wird der C-Cast trotzdem casten und den Pointer zerschießen, während static_cast mit einem Fehler aussteigt.
Das trifft sowohl Up-/Down-/Cross-Casts zu. Insbesondere der erste Fall (Up-Cast) ist subtil, aber trotzdem gefährlich.das zauberwort ist *meistens*. worauf ich mich bezogen habe, war die aussage, dass C-casts immer gefährlich sind, und zwar deshalb:
bei C-casts ists das selbe, die sind weder typsicher noch sonstwas
es ist genau definiert, wann c-casts was tun. das man sich damit ein bein wegschießen *kann*, ist klar.
7H3 N4C3R schrieb:
Insbesondere wenn man sogar in "Lehrbüchern" liest, der Zeiger würde bei einem Up-Cast immer gleich bleiben bei Einfachvererbung.
darüber habe ich mich kurz erst mit einem prof an der uni gestritten; am ende musste er mir zustimmen
wie schnell solche themen doch wiederkommen...
-
queer_boy schrieb:
das zauberwort ist *meistens*. worauf ich mich bezogen habe, war die aussage, dass C-casts immer gefährlich sind, und zwar deshalb:
bei C-casts ists das selbe, die sind weder typsicher noch sonstwas
es ist genau definiert, wann c-casts was tun. das man sich damit ein bein wegschießen *kann*, ist klar.
Schon klar, das hatte ich auch nicht übersehen.
Aber da wir nunmal alle nur Menschen sind, Fehler machen und Dinge übersehen, sind C-Casts ein Spiel mit dem Feuer. Gerade in komplexeren Quellcodes, wo vieleicht noch Templates beteiligt sind, die die verarbeiteten Typen noch nicht mal kennen, kann schnell mal ein unvollständiger Typ reinrutschen. Warum sollte man dann noch einen C-Cast nehmen, wenn man mit C++-Casts immer auf der sicheren Seite ist?
-
7H3 N4C3R schrieb:
Warum sollte man dann noch einen C-Cast nehmen, wenn man mit C++-Casts immer auf der sicheren Seite ist?
Ich denke, wenn man das macht, dann ist das pure (und u.U. gefährliche) Bequemlichkeit.
Btw. finde ich die C++-Casts auch deutlich besser lesbar, da sie einem beim Überfliegen von Codestücken direkt ins Auge fallen.
-
ich gebe euch ja wohl recht; in letzter zeit musste ich allerdings - gezwungenermaßen - sehr viel mit casts arbeiten (uni
) - und bevor ich dreimal in einer zeile mit static_cast von double nach int castet, nehme ich lieber den cast in funktionsnotation (oder - je nachdem - den c-cast); und zwar genau wegen der lesbarkeit (lustigerweise).
sobald vererbung im spiel ist und man castet - selbst wenn es "nur" ein static_cast ist (in dem zusammenhang wahrscheinlich sogar noch als downcast), sollte man sich imo ein bisschen mehr gedanken darüber machen als bloß "geht nicht, dann halt casten".
-
Vielen Dank für eure ausführlichen Erläuterungen bis jetzt!
Dass
reinterpret_cast
meistens undefiniert ist, hab ich jetzt nicht gewusst. Zumal ich ihn eigentlich nie benutze.
Wo setzt manreinterpret_cast
noch am ehesten ein? Wenn einen die Bitmuster bestimmter Variablen interessieren?
-
Typischerweise ist es notwendig in bestimmten APIs (hier WinAPI) die Zeiger auf Objekte und Strukturen mit entsprechenden LPARAM/WPARAM/LRESULT Werten (32bit/64bit Wert) übergeben.
Diese Werte muss man letzten Endes mit reinterpret_cast auf den eigentlichen Zeigertyp bringen.
-
Nexus schrieb:
Vielen Dank für eure ausführlichen Erläuterungen bis jetzt!
Dass
reinterpret_cast
meistens undefiniert ist, hab ich jetzt nicht gewusst. Zumal ich ihn eigentlich nie benutze.
Wo setzt manreinterpret_cast
noch am ehesten ein? Wenn einen die Bitmuster bestimmter Variablen interessieren?Wir müssen in unserer Anwendung Pointer auf verschiedene Handles an LabView weitergeben. LabView kennt aber keine Pointer, so dass wir diese nach long konvertieren und in C-/C++-Funktionen, denen sie übergeben werden, wieder zurückcasten. In unserem Source wird das zwar immer über C-Casts gemacht (zum großen Teil sind das auch C-Funktionen), trotzdem wäre das ein typischer Fall für den reinterpret_cast.