Ist es legal, baumelnde Zeiger zu vergleichen?

70

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 pund qauf Objekte zeigen, die bereits verschwunden sind. Ist das legal?

Fredoverflow
quelle
11
Definieren Sie "legal".
Rechtsfalte
5
Zumindest nicht undefiniertes Verhalten.
Welpe
43
@rightfold Bin ich dem Risiko ausgesetzt, von einem Sprachanwalt eine Unterlassungserklärung zu erhalten?
Fredoverflow
5
Als Datenpunkt optimiert gcc int*f(){int a;return &a;}auf return 0;.
Marc Glisse
5
Ich würde gerne wissen, wozu das gut ist
Ed Heal

Antworten:

57

Einleitung: Die erste Frage ist, ob es legal ist, den Wert von püberhaupt zu verwenden.

Nach a zerstört worden ist , perwirbt , was als bekannt ist ungültig Zeigerwert . Zitat aus N4430 (zur Diskussion des Status von N4430 siehe "Hinweis" unten):

Wenn das Ende der Dauer eines Speicherbereichs erreicht ist, werden die Werte aller Zeiger, die die Adresse eines Teils des freigegebenen Speichers darstellen, zu ungültigen Zeigerwerten .

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):

Die Indirektion durch einen ungültigen Zeigerwert und die Übergabe eines ungültigen Zeigerwerts an eine Freigabefunktion haben ein undefiniertes Verhalten. Jede andere Verwendung eines ungültigen Zeigerwerts hat ein implementierungsdefiniertes Verhalten .

[ Fußnote: Einige Implementierungen definieren möglicherweise, dass das Kopieren eines ungültigen Zeigerwerts einen vom System generierten Laufzeitfehler verursacht. - Fußnote beenden]

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]:

Wenn eine lWert-zu-r-Wert-Konvertierung auf einen Ausdruck e angewendet wird und [...] das Objekt, auf das sich der gl-Wert bezieht, einen ungültigen Zeigerwert enthält, ist das Verhalten implementierungsdefiniert.


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 Bewertung pundq Implementierung definiert ist und dass die Implementierung nicht definiert, dass ein Hardware-Trap auftritt. [expr.eq] / 2 sagt:

Zwei Zeiger werden gleich verglichen, wenn beide null sind, beide auf dieselbe Funktion zeigen oder beide dieselbe Adresse darstellen (3.9.2), andernfalls werden sie ungleich verglichen.

Da es implementierungsdefiniert ist, welche Werte wann erhalten pund qausgewertet 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 -OSchalter konnte ich entweder 1oder sagen lassen 0, ob dieselbe Speicheradresse bnach ader 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 deleteverwendet. 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:

Entwurfsnotiz: Dies sollte für alle Speicherdauern gelten, die enden können, nicht nur für die dynamische Speicherdauer. In einer Implementierung, die Threads oder segmentierte Stapel unterstützt, verhalten sich Thread und automatischer Speicher möglicherweise genauso wie dynamischer Speicher.


Meine Meinung: Ich sehe keine konsistente Möglichkeit, den Standard (vor N4430) zu interpretieren, außer zu sagen, dass er peinen 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.


MM
quelle
24
@LightnessRacesinOrbit Bitte kaufen Sie mir eine Kopie des Standards, damit ich das tun kann (es wäre großartig, wenn Sie mir eine gedruckte Kopie zusenden könnten, damit ich den tatsächlichen Standard in meinen Antworten anstelle nur seines Inhalts anzeigen kann , der zu sein scheint ohne Relevanz für Sie (den Inhalt meine ich). Übrigens, Filip sagt, er würde sich auch für eine gedruckte Ausgabe interessieren.
Griwes
19
Der Rest von uns kauft den Standard nicht. Wir zitieren den neuesten frei verfügbaren Entwurf, normalerweise FDIS oder so, aber der Wortlaut solcher Angelegenheiten ändert sich nicht wesentlich.
Welpe
19
@LightnessRacesinOrbit Wenn Sie den Unterschied zwischen einem Nxxxx-Dokument, einem FDIS und einem offiziellen Standard kennen, sollten Sie die N-Nummer erkennen, die der engsten Annäherung an den offiziellen Standard entspricht, der online kostenlos online verfügbar ist. Es ist lächerlich zu erwarten, dass die Leute mehrere hundert Dollar ausgeben, um ein wenig überzeugender zu sein, was einem Bar-Bet-Argument gleichkommt.
zwol
13
@zwol: Eigentlich ist es durchaus vernünftig, eine Eintrittsbarriere festzulegen, um jemanden in einem Bar-Bet-Argument abzuschießen. Der Punkt ist zu gewinnen, nicht richtig zu sein ;-) Wenn es darum ging, die richtige Antwort zu finden, hätte Lightness natürlich sagen können "... und der veröffentlichte Standard ist der gleiche / andere", anstatt zu versuchen, ihn zu diskreditieren das Zitat ohne es zu ersetzen. Ich meine, ich denke, Leichtigkeit ist richtig, aber das Problem mit Filip's Zitaten ist, dass sie seine Behauptungen nicht unterstützen, nicht, dass sie ungenau sind.
Steve Jessop
9
@LightnessRacesinOrbit Persönlich bin ich mit N3936-Zitaten ganz in Ordnung, es sei denn, jemand mit einer Kopie von C ++ 14 tritt speziell ein und weist auf einen Unterschied hin (von denen es, AFAIK, keinen gibt). Gleiches gilt für C ++ 11 und N3337.
MM
4

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

Superkatze
quelle
-3

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.

user2038893
quelle
Es ist nicht einfach "legal", es ist "implementierungsdefiniert".
Mark Hurd
1
Das Ergebnis von (p == q) ist "implementierungsdefiniert", da stimme ich zu.
user2038893