Gründe für UB bei Derived* -> void* -> Base*
-
Hallo,
ich habe mir ein Beispiel "konstruiert", warum die Cast-Reihenfolge Derived* -> void* -> Base* UB erzeugt, und würde gerne von euch wissen, ob es stimmt und ob es noch weitere Beispiele/Gründe gibt.
sizeof()-Größen sind dabei von mir festgelegt, da es ja nur ein Beispiel ist.Beispiel:
class Base1 { void *bas1_1; public: void some_base1_func(); }; class Base2 { void *bas2_1; public: void some_base2_func(); }; class Derived1 : public Base1, public Base2 { void *der1_1; public: void some_derived_func(); //kein vtable diesmal }; int main() { Derived1 var; Base2 *regular_base2_ptr = &var; void *ptr = &var; Base2 *irregular_base2_ptr = reinterpret_cast<Base2 *>(ptr); Derived1 *irregular_derived_ptr = reinterpret_cast<Derived1 *>(irregular_base2_ptr); //dynamic_cast würde womöglich gar nicht mehr funktionieren }
Nun das Speicherbild der Pointer
regular_base2_ptr:
| ... | bas1_1 | bas2_1 | der1_1 | | regular_base2_ptr
ptr:
| ... | bas1_1 | bas2_1 | der1_1 | | ptr
irregular_base2_ptr:
| ... | bas1_1 | bas2_1 | der1_1 | | irregular_base2_ptr
irregular_derived_ptr:
| ... | bas1_1 | bas2_1 | der1_1 | | irregular_derived_ptr
Erklärung:
Da Base2 weiter hinten im Speicher steht, wird beim Cast von Derived1 auf Base2 der Pointer um sizeof(Base1) inkrementiert. Entsprechend wird beim gegenteiligen Cast der Pointer dekrementiert. Da beim Umweg über void* die Typ-Information verloren geht, funktioniert das nicht mehr.mfg,
wxSkip
-
Seit wann wird bei einem reinterpret_cast der Wert der variablen verändert? O_o
http://www.cplusplus.com/doc/tutorial/typecasting/ schrieb:
reinterpret_cast converts any pointer type to any other pointer type, even of unrelated classes. The operation result is a simple binary copy of the value from one pointer to the other. All pointer conversions are allowed: neither the content pointed nor the pointer type itself is checked.
Also meiner Meinung nach erzeugt dein Code kein UB.
Dein "irregular_derived_ptr" hat genau den gleichen Wert wie "ptr".
-
http://www.cplusplus.com/doc/tutorial/typecasting/ schrieb:
reinterpret_cast converts any pointer type to any other pointer type, even of unrelated classes. The operation result is a simple binary copy of the value from one pointer to the other.
Das ist ziemlicher Blödsinn, sofern es um Standard C++ gehen soll.
ISO IEC 14882:2003 schrieb:
5.2.10/3
The mapping performed by reinterpret_cast is implementation-defined. [Note: it might, or might not, produce a representation different from the original value. ]Also nichts da mit binärer Kopie (per se).
ISO IEC 14882:2003 schrieb:
5.2.10/7
A pointer to an object can be explicitly converted to a pointer to an object of different type.65) 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.Unspezifiziert ist schon mal schlecht. Allerdings...
ISO IEC 14882:2003 schrieb:
5.2.10/10
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)obwohl es um Referenzcasts geht, lässt dieser Absatz auch Rückschlüsse zu Zeigercasts zu.
So oder so ist die Frage des OP so unpräzise gestellt, dass ich nicht sehe, wie sie zu beantworten wäre.
-
AFAIK ist Derived* > void* > Base2* UB.
-
Nach einem kleinen Blick in den 2003er Standard muss ich dir Recht geben,
aber da is auch ziemlich alles Implementation defined...Also da steht z.B.
The mapping performed by reinterpret_cast is implementation-defined. [Note: it might, or might
not, produce a representation different from the original value. ]Das wiederum sagt mir, dass es egal ist, ob ich mit Zeigern rumcaste, die in einer Vererbungshierarchie stehen, oder einfach irgendwelche Pointer caste...
Der Compiler-Hersteller kann praktisch machen, was er will...Microsoft sagt zu dem Thema (siehe MSDN):
The result of a reinterpret_cast cannot safely be used for anything other than being cast back to its original type. Other uses are, at best, nonportable.
Das allerdings gibt meiner vorherigen These Recht... (zumindest für MSVC)
Dein "irregular_derived_ptr" hat genau den gleichen Wert wie "ptr".
Denn obwohl dies über mehrere Ecken geschieht, ist es im Grunde genommen nichts weiter, als ein cast zu einem anderen Typ und wieder zurück.
Was die gängige Praxis angeht, denke ich allerdings durchaus, dass cplusplus.com Recht hat (sonst wäre diese Seite mal in irgend einer FAQ zu blacklisten :S)
Denn mir ist bisher weder mit MSVC noch mit GCC passiert, dass reinterpret_cast die Representation eines Zeiger verändert hat...
-
Okay. dynamic_cast hätte den Wert des Pointers vermutlich verändert, da aber der übergebene Pointer schon auf etwas falsches zeigt, hätte dynamic_cast evtl. 0 zurückgegeben. Gibt es irgendeinen Cast (C-Cast?), der ohne Prüfung den Pointer erwartungsgemäß verändern würde?
@theta: Das weiß ich, die Frage siehe @camper
@camper:
Die Frage ist, aus welchem Grund der Cast Derived* -> void* -> Base* UB ist. Ich habe versucht, ein Fallbeispiel zu geben, nun wollte ich wissen, ob das Beispiel ein Grund dafür ist, dass diese Konversion UB ist, ob meine Annahmen im Allgemeinen stimmen und ob es noch weitere Beispiele/Gründe gibt.
-
wxSkip schrieb:
@camper:
Die Frage ist, aus welchem Grund der Cast Derived* -> void* -> Base* UB ist.Das ist nicht undefiniert. Der erzeugten Pointer dann irgenwie zu benutzen hat u.U. UB zur Folge.
-
camper schrieb:
wxSkip schrieb:
@camper:
Die Frage ist, aus welchem Grund der Cast Derived* -> void* -> Base* UB ist.Das ist nicht undefiniert. Der erzeugten Pointer dann irgenwie zu benutzen hat u.U. UB zur Folge.
Okay, das meinte ich ja. Was bedeutet dein u.U.? Könnten wir dann zur Frage zurückkommen?
-
Heißt das, dass wenn ich folgendes mache ich auch UB kriege?
class Interface1 { virtual void if1Method() = 0; }; class Interface2 { virtual void if2Method() = 0; }; class A : public Interface1, public Interface2 { void aMethod1(); virtual void aMethod2 = 0; virtual void if2Method(); }; class B : public A { void bMethod(); virtual void if1Method(); }; int main() { B b; void * userData = &b; A * a = static_cast<A*>(userData); //hier werden methoden für a aufgerufen... //undefiniertes verhalten? } //alternative main, ist das besser? int main() { B b; A * aPtr = static_cast<A*>(&b); void * userData = aPtr; A * a = static_cast<A*>(userData); //hier werden methoden für a aufgerufen... }
Edit: Habt ihr vll nen link zu nem artikel, wo man mehr über diese Problematik erfährt?
-
Die 1. Variante ist UB, da du B* -> void* -> A* machst.
Die 2. Variante ("alternative") ist OK. Da machst du B* -> A* -> void* -> A*.
Da du für B* -> A* einen static_cast verwendest, geht das OK. A* -> void* -> A* ist auch garantiertermassen OK. Wenn das nicht erlaut wäre, könnte man void-Zeiger ja für gar nichts sinnvoll einsetzen.Edit: Habt ihr vll nen link zu nem artikel, wo man mehr über diese Problematik erfährt?
Hab' ich jetzt nicht zur Hand, weiss aber auch nicht was es dazu gross zu sagen/lesen gäbe.
-
Oups... das mit dem static_cast statt reinterpret_cast/dynamic_cast hab ich jetzt auch im Meyers gelesen. Dachte, der wäre strenger. Links? Hab ich nicht, bin bloß mal beim Versuch einer eigenen shared_ptr<>-Implementierung darauf hingewiesen worden.