AFAIK, obwohl wir kein statisches Speicherarray der Größe 0 erstellen können, können wir dies mit dynamischen tun:
int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined
Wie ich gelesen habe, p
verhält es sich wie ein One-Past-End-Element. Ich kann die Adresse drucken, auf die p
zeigt.
if(p)
cout << p << endl;
Ich bin mir zwar sicher, dass wir diesen Zeiger (nach dem letzten Element) nicht dereferenzieren können, wie wir es mit Iteratoren (nach dem letzten Element) nicht können, aber ich bin mir nicht sicher, ob ich diesen Zeiger inkrementiere
p
? Ist ein undefiniertes Verhalten (UB) wie bei Iteratoren?p++; // UB?
c++
pointers
undefined-behavior
dynamic-arrays
Itachi Uchiwa
quelle
quelle
std::vector
mit 0 darin enthaltenen Element.begin()
ist bereits gleich,end()
sodass Sie einen Iterator, der auf den Anfang zeigt, nicht inkrementieren können.Antworten:
Zeiger auf Elemente von Arrays dürfen auf ein gültiges Element oder auf ein Element nach dem Ende verweisen. Wenn Sie einen Zeiger so erhöhen, dass er über das Ende hinausgeht, ist das Verhalten undefiniert.
Zeigt für Ihr Array der Größe 0
p
bereits eins nach dem Ende, sodass das Inkrementieren nicht zulässig ist.Siehe C ++ 17 8.7 / 4 bezüglich des
+
Operators (++
hat die gleichen Einschränkungen):quelle
x[i]
ist also der gleiche wiex[i + j]
bei beideni
undj
hat den Wert 0?x[i]
ist das gleiche Element wiex[i+j]
wennj==0
.Ich denke, Sie haben bereits die Antwort; Wenn Sie etwas genauer hinschauen: Sie haben gesagt, dass das Inkrementieren eines Off-the-End-Iterators UB ist. Diese Antwort lautet: Was ist ein Iterator?
Der Iterator ist nur ein Objekt, das einen Zeiger hat, und das Inkrementieren dieses Iterators erhöht wirklich den Zeiger, den er hat. Daher wird ein Iterator in vielen Aspekten als Zeiger behandelt.
Dies ist aus der C ++ Primer 5 Edition von Lipmann.
Also ist es UB, tu es nicht.
quelle
Im strengsten Sinne ist dies kein undefiniertes Verhalten, sondern implementierungsdefiniert. Obwohl dies nicht ratsam ist, wenn Sie Nicht-Mainstream-Architekturen unterstützen möchten, können Sie dies wahrscheinlich tun.
Das von interjay gegebene Standardzitat ist gut und zeigt UB an, aber es ist meiner Meinung nach nur der zweitbeste Treffer, da es sich um Zeiger-Zeiger-Arithmetik handelt (komischerweise ist einer explizit UB, der andere nicht). Es gibt einen Absatz, der sich direkt mit der Operation in der Frage befasst:
Oh, warte einen Moment, ein vollständig definierter Objekttyp? Das ist alles? Ich meine wirklich, Typ ? Sie brauchen also überhaupt kein Objekt?
Es erfordert einiges an Lesen, um tatsächlich einen Hinweis darauf zu finden, dass etwas darin möglicherweise nicht ganz so genau definiert ist. Denn bis jetzt liest es sich so, als ob Sie es vollkommen dürfen, ohne Einschränkungen.
[basic.compound] 3
gibt eine Aussage darüber ab, welchen Zeigertyp man haben kann, und da keiner der anderen drei ist, würde das Ergebnis Ihrer Operation eindeutig unter 3.4 fallen: ungültiger Zeiger .Es heißt jedoch nicht, dass Sie keinen ungültigen Zeiger haben dürfen. Im Gegenteil, es werden einige sehr häufige normale Bedingungen (z. B. Ende der Speicherdauer) aufgeführt, unter denen Zeiger regelmäßig ungültig werden. Das ist anscheinend eine zulässige Sache. Und in der Tat:
Wir machen dort ein "beliebiges anderes", es ist also kein undefiniertes Verhalten, sondern implementierungsdefiniert und daher im Allgemeinen zulässig (es sei denn, die Implementierung sagt ausdrücklich etwas anderes aus).
Leider ist das nicht das Ende der Geschichte. Obwohl sich das Nettoergebnis von nun an nicht mehr ändert, wird es umso verwirrender, je länger Sie nach "Zeiger" suchen:
Lesen Sie als: OK, wen interessiert das? Solange ein Zeiger irgendwo in der Erinnerung zeigt , bin ich gut?
Lesen Sie als: OK, sicher abgeleitet, was auch immer. Es erklärt weder, was das ist, noch sagt es, dass ich es tatsächlich brauche. Sicher abgeleitet. Anscheinend kann ich immer noch nicht sicher abgeleitete Zeiger haben. Ich vermute, dass eine Dereferenzierung wahrscheinlich keine so gute Idee wäre, aber es ist durchaus zulässig, sie zu haben. Es sagt nichts anderes.
Oh, also ist es vielleicht egal, was ich dachte. Aber warte ... "darf nicht"? Das heißt, es kann auch . Wie soll ich wissen?
Warten Sie, also ist es sogar möglich, dass ich
declare_reachable()
jeden Zeiger aufrufen muss? Wie soll ich wissen?Jetzt können Sie in konvertieren
intptr_t
, was genau definiert ist und eine ganzzahlige Darstellung eines sicher abgeleiteten Zeigers liefert. Da es sich natürlich um eine Ganzzahl handelt, ist es absolut legitim und klar definiert, sie nach Belieben zu erhöhen.Und ja, Sie können den
intptr_t
Rücken in einen Zeiger konvertieren , der ebenfalls gut definiert ist. Da dies nicht der ursprüngliche Wert ist, kann nicht mehr garantiert werden, dass Sie (offensichtlich) über einen sicher abgeleiteten Zeiger verfügen. Alles in allem ist dies jedoch, wie in der Implementierung definiert, nach dem Buchstaben des Standards eine zu 100% legitime Sache:Der Fang
Zeiger sind nur gewöhnliche ganze Zahlen, nur Sie verwenden sie zufällig als Zeiger. Oh, wenn das nur wahr wäre!
Leider gibt es Architekturen, in denen dies überhaupt nicht zutrifft, und das bloße Generieren eines ungültigen Zeigers (nicht dereferenzieren, nur in einem Zeigerregister haben) führt zu einer Falle.
Das ist also die Basis für "Implementierung definiert". Das, und die Tatsache , dass ein Zeiger erhöht wird, wann immer Sie wollen, als Sie bitte könnte natürlich Ursache Überlauf, der die Norm nicht behandeln will. Der Adressraum am Ende der Anwendung fällt möglicherweise nicht mit dem Ort des Überlaufs zusammen, und Sie wissen nicht einmal, ob es einen Überlauf für Zeiger auf eine bestimmte Architektur gibt. Alles in allem ist es ein albtraumhaftes Durcheinander, das in keiner Beziehung zu den möglichen Vorteilen steht.
Der Umgang mit der One-Past-Object-Bedingung auf der anderen Seite ist einfach: Die Implementierung muss einfach sicherstellen, dass niemals ein Objekt zugewiesen wird, damit das letzte Byte im Adressraum belegt ist. Das ist klar definiert, da es nützlich und trivial ist, dies zu garantieren.
quelle
+
Operator gefunden hat (von dem aus++
fließt), was bedeutet, dass das Zeigen nach "one-after-the-end" undefiniert ist.