Undefiniertes Verhalten bei const_cast

7

Ich hatte gehofft, dass jemand genau erklären könnte, was mit undefiniertem Verhalten in C ++ gemeint ist. Angesichts der folgenden Klassendefinition:

%Vor%

Wenn ich richtig verstanden habe, entfernen die beiden const_casts sowohl für einen Verweis als auch für einen Zeiger im folgenden Code die Const-ness des ursprünglichen Objekts vom Typ Foo, aber alle Versuche, dieses Objekt durch den Zeiger oder zu ändern Die Referenz führt zu undefiniertem Verhalten.

%Vor%

Was mich verwirrt, ist der Grund, warum dies zu funktionieren scheint und das ursprüngliche const-Objekt modifizieren wird, aber mich nicht einmal mit einer Warnung dazu aufruft, mir mitzuteilen, dass dieses Verhalten nicht definiert ist. Ich weiß, dass Const_casts im Großen und Ganzen verpönt sind, aber ich kann mir einen Fall vorstellen, in dem die Unkenntnis, dass C-Style-Cast zu einem const_cast führen kann, ohne bemerkt zu werden, zum Beispiel:

%Vor%

Unter welchen Umständen kann dieses Verhalten einen schwerwiegenden Laufzeitfehler verursachen? Gibt es eine Compiler-Einstellung, die ich einstellen kann, um mich vor diesem (potenziell) gefährlichen Verhalten zu warnen?

NB: Ich benutze MSVC2008.

    
Munro 08.09.2011, 14:21
quelle

7 Antworten

11
  

Ich hatte gehofft, dass jemand genau erklären könnte, was mit undefiniertem Verhalten in C ++ gemeint ist.

Technisch gesehen bedeutet "nicht definiertes Verhalten", dass die Sprache keine Semantik definiert, um so etwas zu tun.

In der Praxis bedeutet dies normalerweise " tu es nicht ; es kann brechen, wenn der Compiler Optimierungen vornimmt oder aus anderen Gründen".

  

Was mich verwirrt, ist warum das scheinbar funktioniert und das ursprüngliche const-Objekt ändert, aber ich werde nicht einmal mit einer Warnung darauf hingewiesen, dass dieses Verhalten nicht definiert ist.

In diesem speziellen Beispiel scheint der Versuch, ein nicht veränderbares Objekt zu ändern, scheinbar "zu funktionieren", oder es überschreibt Speicher, der nicht zum Programm gehört oder zu einem anderen Objekt gehört, weil der Nicht veränderbare Objekte wurden möglicherweise zur Kompilierungszeit entfernt oder in einigen schreibgeschützten Datensegmenten im Speicher vorhanden.

Die Faktoren, die dazu führen können, dass diese Dinge passieren, sind einfach zu komplex, um sie aufzulisten. Betrachten Sie den Fall der Dereferenzierung eines nicht initialisierten Zeigers (auch UB): Das "Objekt", mit dem Sie arbeiten, wird eine beliebige Speicheradresse haben, die davon abhängt, welcher Wert sich im Speicher des Zeigers befindet; Dieser "Wert" ist möglicherweise abhängig von früheren Programmaufrufen, früheren Arbeiten im selben Programm, Speicherung von vom Benutzer bereitgestellten Eingaben usw. Es ist einfach nicht möglich, zu versuchen, die möglichen Ergebnisse der Aufdeckung von Undefined Behavior zu rationalisieren. t störe und sage stattdessen " mach es nicht ".

  

Was mich verwirrt, ist, warum dies funktioniert und das ursprüngliche const-Objekt ändert, aber mich nicht einmal mit einer Warnung dazu auffordert, mir mitzuteilen, dass dieses Verhalten nicht definiert ist.

Als weitere Komplikation sind Compiler nicht erforderlich für die Diagnose (Warnungen / Fehler ausgeben) für undefiniertes Verhalten, weil Code, der Undefined Behavior aufruft, nicht mit Code identisch ist, der schlecht formatiert ist (d ausdrücklich illegal). In vielen Fällen ist es für den Compiler nicht möglich, sogar UB zu erkennen, daher ist dies ein Bereich, in dem der Programmierer den Code richtig schreiben muss.

Das Typsystem - einschließlich Existenz und Semantik des Schlüsselworts const - bietet grundlegenden Schutz gegen das Schreiben von Code, der bricht; Ein C ++ - Programmierer sollte sich stets bewusst sein, dass dieses System - z. indem Sie const ness hacken - geschieht auf eigene Gefahr und ist in der Regel eine schlechte Idee. ™

  

Ich kann mir einen Fall vorstellen, in dem es nicht bewusst ist, dass C-Style-Cast dazu führen kann, dass ein const_cast gemacht wird, ohne bemerkt zu werden.

Absolut. Wenn die Warnungsstufen hoch genug eingestellt sind, kann ein vernünftiger Compiler Sie davor warnen, aber es muss nicht und darf es auch nicht. Im Allgemeinen ist dies ein guter Grund, warum C-Style Casts verpönt sind, aber sie werden immer noch unterstützt, um rückwärtskompatibel mit C zu sein. Es ist nur eines dieser unglücklichen Dinge.

    
Lightness Races in Orbit 08.09.2011, 14:32
quelle
4

Undefiniertes Verhalten bedeutet genau das: Verhalten, das nicht durch den Sprachstandard definiert ist. Es tritt normalerweise in Situationen auf, in denen der Code etwas falsch macht, aber der Fehler kann nicht vom Compiler erkannt werden. Die einzige Möglichkeit, den Fehler zu finden, wäre die Einführung eines Laufzeittests, der die Leistung beeinträchtigen würde. Stattdessen sagt Ihnen die Sprachspezifikation, dass Sie bestimmte Dinge nicht tun dürfen, und wenn Sie dies tun, dann könnte alles passieren.

Wenn Sie in ein konstantes Objekt schreiben, indem Sie const_cast verwenden, um die Prüfungen zur Kompilierungszeit zu unterlaufen, gibt es drei mögliche Szenarien:

  • es wird genau wie ein nicht-konstantes Objekt behandelt, und das Schreiben darauf modifiziert es;
  • es befindet sich in einem schreibgeschützten Speicher, und das Schreiben darauf verursacht einen Schutzfehler;
  • es wird (während der Optimierung) durch konstante Werte ersetzt, die in den kompilierten Code eingebettet sind, so dass es nach dem Schreiben immer noch seinen Anfangswert hat.

In Ihrem Test landeten Sie im ersten Szenario - das Objekt wurde (fast sicher) auf dem Stack erstellt, der nicht schreibgeschützt ist. Sie können feststellen, dass Sie das zweite Szenario erhalten, wenn das Objekt statisch ist, und das dritte, wenn Sie mehr Optimierung aktivieren.

Im Allgemeinen kann der Compiler diesen Fehler nicht diagnostizieren - es gibt keine Möglichkeit zu sagen (außer in sehr einfachen Beispielen wie Ihrem), ob das Ziel einer Referenz oder eines Zeigers konstant ist oder nicht. Es liegt an Ihnen, sicherzustellen, dass Sie const_cast nur verwenden, wenn Sie garantieren können, dass es sicher ist - entweder wenn das Objekt nicht konstant ist oder wenn Sie es sowieso nicht ändern.

    
Mike Seymour 08.09.2011 14:39
quelle
4
  

Was mich verwirrt, ist, warum dies scheint zu funktionieren

Das ist, was undefiniertes Verhalten bedeutet.
Es kann alles tun, einschließlich scheinen, zu funktionieren.
Wenn Sie das Optimierungslevel auf den höchsten Wert erhöhen, wird es wahrscheinlich nicht mehr funktionieren.

  

, gibt mir aber nicht einmal eine Warnung, dass dieses Verhalten nicht definiert ist.

An der Stelle, wo es war, ist die Modifikation das Objekt nicht const. Im allgemeinen Fall kann es nicht sagen, dass das Objekt ursprünglich ein const war, deshalb ist es nicht möglich, Sie zu warnen. Auch wenn es sich um eine Aussage handelte, wird diese unabhängig von den anderen ausgewertet (wenn man sich diese Art von Warngenerierung anschaut).

Zweitens, indem Sie cast verwenden, sagen Sie dem Compiler "Ich weiß, was ich mache, überschreibe alle Sicherheitsfunktionen und tue es einfach" .

Zum Beispiel funktioniert das folgende gut: (oder wird auch scheinen (in der nasalen Deamon Art von Weg))

%Vor%
  

Ich weiß, dass const_casts im Großen und Ganzen verpönt sind auf

Das ist der falsche Weg, sie anzusehen. Sie sind eine Möglichkeit, in dem Code zu dokumentieren, dass Sie etwas Seltsames tun, das von intelligenten Leuten validiert werden muss (da der Compiler der Besetzung ohne Frage gehorcht). Der Grund, warum Sie eine intelligente Person zur Validierung benötigen, ist, dass sie zu undefiniertem Verhalten führen kann, aber das Gute, was Sie jetzt explizit in Ihrem Code dokumentiert haben (und die Leute werden sich genau anschauen, was Sie getan haben).

  

aber ich kann mir einen Fall vorstellen, in dem die Ungewissheit, dass C-Style-Cast dazu führen kann, dass ein const_cast gemacht wird, ohne bemerkt zu werden, zum Beispiel:

In C ++ muss kein C-Style-Cast verwendet werden.
Im schlimmsten Fall kann der C-Style Cast durch reinterpret_cast & lt; & gt; Aber wenn Sie Code portieren, möchten Sie sehen, ob Sie static_cast & lt; & gt; verwendet haben könnten. Der Punkt der C ++ - Umwandlungen besteht darin, sie hervorzuheben , damit Sie sie sehen und auf einen Blick den Unterschied zwischen den gefährlichen und den gutartigen Umwandlungen erkennen können.

    
Martin York 08.09.2011 14:59
quelle
3

Undefiniertes Verhalten hängt davon ab, wie das Objekt erstellt wurde . Sie können Stephan erklären es um 00:10:00, aber im Wesentlichen, folgen Sie den folgenden Code:

%Vor%

Nun gibt es zwei Fälle für den Aufruf von f

%Vor%

Zusammenfassend wurde K als non const geboren, also ist die Besetzung in Ordnung, wenn f aufgerufen wird, während AK als const geboren wurde, also ... UB ist es.

    
Nikos Athanasiou 08.03.2014 16:55
quelle
2

Ein klassisches Beispiel wäre der Versuch, ein Konst-String-Literal zu ändern, das in einem geschützten Datensegment vorhanden sein kann.

    
Andreas Brinck 08.09.2011 14:24
quelle
0

Compiler können Const-Daten aus Gründen der Optimierung in Nur-Lese-Teile des Speichers plazieren und versuchen, diese Daten zu modifizieren, was zu UB führt.

    
ks1322 08.09.2011 14:28
quelle
0

Statische und const Daten werden oft in einem anderen Teil Ihres Programms gespeichert als lokale Variablen. Bei const-Variablen befinden sich diese Bereiche häufig im schreibgeschützten Modus, um die Konstanz der Variablen zu erzwingen. Der Versuch, in einen Nur-Lese-Speicher zu schreiben, führt zu einem "undefinierten Verhalten", da die Reaktion von Ihrem Betriebssystem abhängt. "Undefined beheavior" bedeutet, dass die Sprache nicht angibt, wie dieser Fall behandelt werden soll.

Wenn Sie eine ausführlichere Erklärung zum Speicher wünschen, empfehle ich Ihnen dies . Es ist eine Erklärung basierend auf UNIX, aber ähnliche Mechanismen werden auf allen Betriebssystemen verwendet.

    
Opera 08.09.2011 14:35
quelle