Ist es legal, baumelnde Zeiger zu vergleichen?
int *p, *q;
{
int a;
p = &a;
}
{
int b;
q = &b;
}
std::cout << (p == q) << '\n';
Beachten Sie, wie beide p
und q
auf Objekte zeigen, die bereits verschwunden sind. Ist das legal?
c++
pointers
language-lawyer
dangling-pointer
Fredoverflow
quelle
quelle
int*f(){int a;return &a;}
aufreturn 0;
.Antworten:
Einleitung: Die erste Frage ist, ob es legal ist, den Wert von
p
überhaupt zu verwenden.Nach
a
zerstört worden ist ,p
erwirbt , was als bekannt ist ungültig Zeigerwert . Zitat aus N4430 (zur Diskussion des Status von N4430 siehe "Hinweis" unten):Das Verhalten bei Verwendung eines ungültigen Zeigerwerts wird ebenfalls im selben Abschnitt von N4430 behandelt (und nahezu identischer Text wird in C ++ 14 [basic.stc.dynamic.deallocation] / 4 angezeigt):
Sie müssen daher die Dokumentation Ihrer Implementierung konsultieren, um herauszufinden, was hier geschehen soll (seit C ++ 14).
Der Begriff wird in den obigen Anführungszeichen verwendet Mitteln erforderlich lvalue-to-rvalue Umwandlung, wie in C ++ 14 [conv.lval / 2]:
Verlauf: In C ++ 11 wurde dies eher als undefiniert als als implementierungsdefiniert bezeichnet . es wurde von DR1438 geändert . Die vollständigen Anführungszeichen finden Sie im Bearbeitungsverlauf dieses Beitrags.
Anwendung auf
p == q
: Angenommen, wir haben in C ++ 14 + N4430 akzeptiert, dass das Ergebnis der Bewertungp
undq
Implementierung definiert ist und dass die Implementierung nicht definiert, dass ein Hardware-Trap auftritt. [expr.eq] / 2 sagt:Da es implementierungsdefiniert ist, welche Werte wann erhalten
p
undq
ausgewertet werden, können wir nicht sicher sagen, was hier passieren wird. Es muss jedoch entweder implementierungsdefiniert oder nicht spezifiziert sein.g ++ scheint in diesem Fall ein nicht spezifiziertes Verhalten aufzuweisen; Abhängig vom
-O
Schalter konnte ich entweder1
oder sagen lassen0
, ob dieselbe Speicheradresseb
nacha
der Zerstörung wiederverwendet wurde oder nicht .Hinweis zu N4430: Dies ist eine vorgeschlagene Fehlerbehebung für C ++ 14, die noch nicht akzeptiert wurde. Es bereinigt viele Formulierungen in Bezug auf die Objektlebensdauer, ungültige Zeiger, Unterobjekte, Gewerkschaften und den Zugriff auf Array-Grenzen.
Im C ++ 14-Text wird unter [basic.stc.dynamic.deallocation] / 4 und den folgenden Absätzen definiert, dass ein ungültiger Zeigerwert vorliegt ergibt sich , wenn
delete
verwendet. Es ist jedoch nicht klar angegeben, ob das gleiche Prinzip für die statische oder automatische Speicherung gilt oder nicht.Es gibt eine Definition "gültiger Zeiger" in [basic.compound] / 3, aber sie ist zu vage, um sie sinnvoll zu verwenden. Die [basic.life] / 5 (Fußnote) bezieht sich auf denselben Text, um das Verhalten von Zeigern auf Objekte von zu definieren statische Speicherdauer, was darauf hindeutet, dass sie für alle Speichertypen gelten sollte.
In N4430 wird der Text von diesem Abschnitt um eine Ebene nach oben verschoben, sodass er eindeutig für alle Speicherdauern gilt. Es ist ein Hinweis beigefügt:
Meine Meinung: Ich sehe keine konsistente Möglichkeit, den Standard (vor N4430) zu interpretieren, außer zu sagen, dass er
p
einen ungültigen Zeigerwert erhält. Das Verhalten scheint von keinem anderen Abschnitt als dem, was wir uns bereits angesehen haben, abgedeckt zu werden. Daher bin ich froh, den Wortlaut des N4430 so zu behandeln, dass er in diesem Fall die Absicht des Standards darstellt.quelle
In der Vergangenheit gab es einige Systeme, bei denen die Verwendung eines Zeigers als r-Wert dazu führen kann, dass das System einige Informationen abruft, die durch einige Bits in diesem Zeiger gekennzeichnet sind. Wenn ein Zeiger beispielsweise die Adresse des Headers eines Objekts zusammen mit einem Versatz in das Objekt enthalten könnte, könnte das Abrufen eines Zeigers dazu führen, dass das System auch einige Informationen aus diesem Header abruft. Wenn das Objekt nicht mehr existiert, kann der Versuch, Informationen aus seinem Header abzurufen, mit willkürlichen Konsequenzen fehlschlagen.
Allerdings werden in der überwiegenden Mehrheit der C-Implementierungen alle Zeiger, die zu einem bestimmten Zeitpunkt am Leben waren, für immer die gleichen Beziehungen in Bezug auf die relationalen und Subtraktionsoperatoren haben wie zu diesem bestimmten Zeitpunkt. In der Tat kann man in den meisten Implementierungen, wenn man dies hat
char *p
, bestimmen, ob es einen Teil eines Objekts identifiziert,char *base; size_t size;
indem man prüft, ob(size_t)(p-base) < size
; Ein solcher Vergleich funktioniert auch nachträglich, wenn sich die Lebensdauer der Objekte überschneidet.Leider definiert der Standard weder Mittel, mit denen Code anzeigen kann, dass er eine der letzteren Garantien benötigt, noch gibt es ein Standardmittel, mit dem Code fragen kann, ob eine bestimmte Implementierung eines der letzteren Verhaltensweisen versprechen und die Kompilierung ablehnen kann, wenn dies nicht der Fall ist . Ferner betrachten einige hypermoderne Implementierungen jede Verwendung von relationalen oder Subtraktionsoperatoren für zwei Zeiger als ein Versprechen des Programmierers, dass die fraglichen Zeiger immer dasselbe lebende Objekt identifizieren und jeglichen Code weglassen, der nur relevant wäre, wenn diese Annahme hielt nicht. Obwohl viele Hardwareplattformen in der Lage wären, Garantien anzubieten, die für viele Algorithmen nützlich wären, gibt es folglich
quelle
Die Zeiger enthalten die Adressen der Variablen, auf die sie verweisen. Die Adressen sind auch dann gültig, wenn die Variablen, die dort gespeichert waren, freigegeben / zerstört / nicht verfügbar sind. Solange Sie nicht versuchen, die Werte an diesen Adressen zu verwenden, sind Sie sicher, was bedeutet, dass * p und * q undefiniert sind.
Offensichtlich ist das Ergebnis eine definierte Implementierung. Daher kann dieses Codebeispiel verwendet werden, um die Funktionen Ihres Compilers zu untersuchen, wenn Sie sich nicht mit Assemblycode befassen möchten.
Ob dies eine sinnvolle Praxis ist, ist eine völlig andere Diskussion.
quelle