(In Bezug auf diese Frage und Antwort .)
Vor dem C ++ 17-Standard war der folgende Satz in [basic.compound] / 3 enthalten :
Befindet sich ein Objekt vom Typ T an einer Adresse A, so soll ein Zeiger vom Typ cv T *, dessen Wert die Adresse A ist, auf dieses Objekt zeigen, unabhängig davon, wie der Wert erhalten wurde.
Aber seit C ++ 17 wurde dieser Satz entfernt .
Zum Beispiel glaube ich, dass dieser Satz diesen Beispielcode definiert hat und dass dies seit C ++ 17 ein undefiniertes Verhalten ist:
alignas(int) unsigned char buffer[2*sizeof(int)];
auto p1=new(buffer) int{};
auto p2=new(p1+1) int{};
*(p1+1)=10;
Hält vor C ++ 17 p1+1
die Adresse an *p2
und hat den richtigen Typ, also *(p1+1)
einen Zeiger auf *p2
. In C ++ 17 p1+1
ist es ein Zeiger hinter dem Ende , also kein Zeiger auf ein Objekt, und ich glaube, es ist nicht dereferenzierbar.
Ist diese Interpretation dieser Änderung des Standardrechts oder gibt es andere Regeln, die die Streichung des zitierten Satzes kompensieren?
int a, b = 0;
, Sie können nicht einmal tun*(&a + 1) = 1;
, wenn Sie überprüft haben&a + 1 == &b
. Wenn Sie einen gültigen Zeiger auf ein Objekt erhalten können, indem Sie nur dessen Adresse erraten, wird das Speichern lokaler Variablen in Registern problematisch.Antworten:
Ja, diese Interpretation ist richtig. Ein Zeiger nach dem Ende kann nicht einfach in einen anderen Zeigerwert konvertiert werden, der zufällig auf diese Adresse zeigt.
Die neue [basic.compound] / 3 sagt:
Diese schließen sich gegenseitig aus.
p1+1
ist ein Zeiger hinter dem Ende, kein Zeiger auf ein Objekt.p1+1
zeigt auf eine Hypothesex[1]
eines Arrays der Größe 1 beip1
, nicht aufp2
. Diese beiden Objekte sind nicht zeigerinterkonvertierbar.Wir haben auch den nicht normativen Hinweis:
das klärt die Absicht.
Wie TC in zahlreichen Kommentaren ( insbesondere in diesem ) hervorhebt , ist dies wirklich ein Sonderfall des Problems, das mit dem Implementierungsversuch einhergeht
std::vector
- das heißt, das[v.data(), v.data() + v.size())
muss ein gültiger Bereich sein und dennochvector
kein Array-Objekt erstellen Nur eine definierte Zeigerarithmetik würde von einem bestimmten Objekt im Vektor bis zum Ende seines hypothetischen Arrays mit einer Größe gehen. Weitere Ressourcen finden Sie in CWG 2182 , dieser Standarddiskussion und zwei Überarbeitungen eines Papiers zu diesem Thema: P0593R0 und P0593R1 (speziell Abschnitt 1.3).quelle
vector
Implementierbarkeitsproblems". +1.p1+1
kein Zeiger über das Ende hinaus erstellt, und die gesamte Diskussion über Zeiger nach dem Ende ist umstritten. Ihr spezieller Zwei-Elemente-Sonderfall ist möglicherweise nicht UB vor 17, aber auch nicht sehr interessant.In Ihrem Beispiel
*(p1 + 1) = 10;
sollte UB sein, da es nach dem Ende des Arrays der Größe 1 eins ist. Wir befinden uns hier jedoch in einem ganz besonderen Fall, da das Array dynamisch in einem größeren char-Array erstellt wurde.Die dynamische Objekterstellung wird in 4.5 Das C ++ - Objektmodell [intro.object] , §3 des Entwurfs n4659 des C ++ - Standards beschrieben:
Die 3.3 scheint ziemlich unklar, aber die folgenden Beispiele machen die Absicht klarer:
Im Beispiel bietet das
buffer
Array Speicher für*p1
und*p2
.Die folgenden Absätze beweisen, dass das vollständige Objekt für beide
*p1
und*p2
istbuffer
:Sobald dies festgelegt ist, ist der andere relevante Teil des Entwurfs n4659 für C ++ 17 [basic.coumpound] §3 (betonen Sie meinen):
Der Hinweis Ein Zeiger nach dem Ende ... gilt hier nicht, da die Objekte, auf die durch
p1
undp2
nicht unabhängig verwiesen wird, in dasselbe vollständige Objekt verschachtelt sind, sodass die Zeigerarithmetik innerhalb des Objekts, das Speicher bereitstellt, sinnvoll ist:p2 - p1
definiert ist und ist(&buffer[sizeof(int)] - buffer]) / sizeof(int)
das ist 1.Ist
p1 + 1
also ein Zeiger auf*p2
und*(p1 + 1) = 10;
hat Verhalten definiert und setzt den Wert von*p2
.Ich habe auch den C4-Anhang über die Kompatibilität zwischen C ++ 14 und aktuellen (C ++ 17) Standards gelesen. Das Entfernen der Möglichkeit, Zeigerarithmetik zwischen Objekten zu verwenden, die dynamisch in einem einzelnen Zeichenarray erstellt wurden, wäre eine wichtige Änderung, die IMHO dort zitieren sollte, da dies eine häufig verwendete Funktion ist. Da auf den Kompatibilitätsseiten nichts darüber vorhanden ist, bestätigt dies meines Erachtens, dass es nicht die Absicht des Standards war, dies zu verbieten.
Insbesondere würde diese allgemeine dynamische Konstruktion eines Arrays von Objekten aus einer Klasse ohne Standardkonstruktor zunichte gemacht:
arr
kann dann als Zeiger auf das erste Element eines Arrays verwendet werden ...quelle
vector
kein Array-Objekt erstellt wird (und nicht erstellt werden kann), sondern über eine Schnittstelle verfügt, über die der Benutzer einen Zeiger abrufen kann, der die Zeigerarithmetik unterstützt (die nur für Zeiger auf Array-Objekte definiert ist).Die hier gegebenen Antworten zu erweitern, ist ein Beispiel dafür, was meiner Meinung nach der überarbeitete Wortlaut ausschließt:
Warnung: Undefiniertes Verhalten
Aus vollständig implementierungsabhängigen (und fragilen) Gründen ist die mögliche Ausgabe dieses Programms:
Diese Ausgabe zeigt, dass die zwei Arrays (in diesem Fall) zufällig im Speicher gespeichert sind, so dass 'eins nach dem Ende' von
A
zufällig den Wert der Adresse des ersten Elements von enthältB
.Die überarbeitete Spezifikation stellt sicher, dass unabhängig davon
A+1
niemals ein gültiger Zeiger auf istB
. Der alte Ausdruck "unabhängig davon, wie der Wert erhalten wird" besagt, dass wenn "A + 1" auf "B [0]" zeigt, dies ein gültiger Zeiger auf "B [0]" ist. Das kann nicht gut sein und sicherlich nie die Absicht.quelle
-O0
bei einigen Compilern) Zeiger nicht als triviale Typen. Compiler nehmen die Anforderungen des Standards nicht ernst und auch nicht die Leute, die den Standard schreiben, von einer anderen Sprache träumen und alle Arten von Erfindungen machen, die den Grundprinzipien direkt widersprechen. Offensichtlich sind Benutzer verwirrt und werden manchmal schlecht behandelt, wenn sie sich über Compiler-Fehler beschweren.