2015-02-25 12 views
7

Ich kann nicht finden, wo in der Norm, dass es sagt, daß dieses Programm nicht definiert ist:Ist es undefiniertes Verhalten, einen freien Zeiger zu dereferenzieren?

#include <iostream> 

int main() 
{ 
    int *p; 
    { 
     int n = 45; 
     p = &n; 
    } 
    std::cout << *p; 
} 

Keiner der Fälle in §3.8 Objektlebensdauer scheint hier anzuwenden.

+2

Ich glaube, 'p' wird immer noch auf den Speicherort zeigen, wo' n' war, aber Sie haben keine Möglichkeit zu wissen, was da sein wird. – mstbaum

+0

@mstbaum und wie ist das mit der Frage verbunden? – Slava

+0

@remyabel (Mein vorher gelöschter Kommentar, der gefragt wird, ob "die Ergebnisse nicht vorhersagen können" impliziert UB.) Vielleicht bin ich nicht klar, was undefiniertes Verhalten ist. Meine Argumentation ist wahrscheinlich ähnlich wie bei Mstbaum, da wir nicht wissen, was sich an diesem Ort im Speicher befindet, sodass wir die Ergebnisse nicht vorhersagen können. Reicht das nicht aus? Muss ich im Standard nachsehen, um sicher zu gehen? – eigenchris

Antwort

6

Ich bin nicht 100% sicher wegen der Formulierung, aber es sieht so aus, als ob dies von 3,8/6 abgedeckt ist (der Grund, warum ich diese Interpretation für richtig halte, ist das nicht-normative Beispiel in 3,8/5, // undefined behavior, lifetime of *pb has ended) :

... nachdem die Lebensdauer eines Objekts abgelaufen ist und bevor der Speicher, den das Objekt belegt hat, wiederverwendet oder freigegeben wird, kann jeder glvalue, der sich auf das ursprüngliche Objekt bezieht, verwendet werden. Das Programm hat ein undefiniertes Verhalten wenn:

Dann ist das erste Geschoss der Schuldige: an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,: Diese Umwandlung muss entweder beim Aufruf von operator<< oder schließlich an dem Punkt erfolgen, an dem der Integralwert zur Formatierung innerhalb von ostream gelesen wird.

+0

Keine Ahnung, wo dies im Standard ist, aber da n hier auf dem Stapel lebt, würde man denken, dass die Erinnerung "freigegeben" wird, wenn sie außer Reichweite ist. –

+0

Außerdem verstehe ich unter 3.7.3 ("automatische Speicherdauer"), dass der Speicher, den das Objekt belegt hat, nicht benötigt wird, nachdem der Block beendet wurde. –

+1

Können Sie feststellen, wann ein Speicher * wiederverwendet oder freigegeben wird *, da er von dieser Phrase abhängt. Zumal dies erforderlich ist, geschieht dies * vor *. –

-1

Also zunächst einmal nach 3.7.3 Automatische Lagerdauer Speicherung Ihres Objekts freigegeben wird:

Block Umfang Variablen explizit Register angemeldet oder nicht explizit erklärte statische oder extern automatische Speicherdauer hat . Der Speicher für diese Entitäten dauert so lange, bis der Block, in dem sie erstellt wurden, beendet wird.

Und von 3.8 Object Lebensdauer

Vor der Lebensdauer eines Objekts begonnen hat, aber nach der Lagerung der das Objekt wurde occupy zugeordnet oder nach der Lebensdauer eines Objekts hat beendet und bevor der Speicher, den das Objekt belegt hat, wiederverwendet oder freigegeben wird, darf jeder Zeiger, der auf den Lagerort verweist, wo sich das Objekt befindet oder befand, , jedoch nur in eingeschränkter Weise

so dereferencing Zeiger auf Variable, die Speicher führt zu UB freigegeben

1

, die sicherlich nicht definiertes Verhalten ist (durch den gesunden Menschenverstand, und dem Wortlaut der Norm).

Soweit der Standard geht, 3.8/5 ist ziemlich konkret zu wissen, was erlaubt ist und was nicht ist:

[...] nach der Lebensdauer eines Objekts beendet und vor der Speicher, den das Objekt belegt hat, wiederverwendet oder freigegeben wird, kann jeder Zeiger verwendet werden, der sich auf den Speicherort bezieht, an dem sich das Objekt befindet oder befand [...] und den Zeiger als ob der Zeiger verwenden waren vom Typ void *, ist wohldefiniert.
Indirection [...] ist wie unten beschrieben erlaubt.Das Programm Verhalten, wenn nicht definiert hat:
- ...
- [...] verwendet als Operand static_cast, es sei denn, die Umstellung auf cv auf Zeiger ist void oder void zu cv auf Zeiger und anschließend Zeiger entweder cv cv char oder unsigned char
- [...] als Operand am Ende des Bereichs verwendet dynamic_cast

die Objektspeicher pro 3.7.3/1 (in der Praxis endet Dies ist höchstwahrscheinlich nicht wahr, der Stack-Frame wird wahrscheinlich am Ende der Funktion zurückgesetzt n, aber formell das ist was passiert). Daher tritt die Dereferenzierung nicht nach Ablauf der Lebenszeit auf sondern vor die Freigabe des Speichers. Es passiert nach Freigabe des Speichers.
Die speziellen Bedingungen, unter denen Sie den Zeiger trotzdem dereferenzieren können, gelten daher nicht (dasselbe gilt für ähnliche Absätze mit derselben Vorbedingung wie 3.8/6).

der weiteren Annahme, daß der vorigen Absatz nicht wahr war, ist es nur zulässig zu dereferenzieren den Zeiger als cv void* oder an cv char (mit oder ohne Vorzeichen) vor dereferenzieren zu werfen. Mit anderen Worten, Sie sind nicht erlaubt, um die spitz zu int zu betrachten, als ob es ein int wäre. Wie in 3.8/5 angegeben, ist die int* wirklich nur eine void* nach der Lebensdauer des Objekts. Das bedeutet, dass die Dereferenzierung als int* einer Umwandlung gleichkommt (nicht explizit, aber immer noch).

Man würde wirklich wünschen, dass dieser Versuch einen Fehler erzeugt, aber ich denke, das ist ein wirklich schwieriger für den Compiler zu erkennen. Der Zeiger selbst ist gut und lebendig und wurde sicher abgeleitet, indem die Adresse eines gültigen Objekts genommen wurde, was wahrscheinlich fast unmöglich zu diagnostizieren ist.

1

*p ist ein glvalue. Der Code cout << *p erfordert eine lvalue-to-rvalue-Konvertierung. Dies wird durch C++ 14 [conv.lval] definiert.

Punkt 2 listet verschiedene Fälle auf und beschreibt das Verhalten in jedem Fall. Keine davon trifft auf *p zu. Insbesondere ist der letzte Punkt:

Andernfalls ist der Wert, der in dem durch den glvalue angegebenen Objekt enthalten ist, das prvalue-Ergebnis.

*p zeigt jedoch kein Objekt an.

Im Abschnitt [basic.life] sind ein paar Fälle, die definieren, was lvalue-to-rvalue Umwandlung tut, über was in [conv.lval] gesagt wird. Diese Fälle beziehen sich darauf, wann Speicher für ein Objekt erhalten wurde, aber wir befinden uns außerhalb der Lebensdauer des Objekts. Sie gelten jedoch nicht für *p, da der Speicher beim Beenden des vorherigen Blocks freigegeben wird.

So, das Verhalten dieses Codes ist nicht definiert durch Weglassen: Nirgendwo in der Norm ist das definieren, was es bedeutet, rvalue Umwandlung auszuführen, wenn der L-Wert kein Objekt angeben und zeigt nicht gültig Lagerung für ein Objekt.


Es kann nicht zufrieden stellend fühlen für etwas „durch Weglassen nicht definiert“ werden wir immer gerne eine konkrete Aussage sehen, „das ist nicht definiertes Verhalten“ sicher zu sein, wir etwas nicht übersehen haben. Aber manchmal ist es so.

Verwandte Themen