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:
[expr.post.incr] / [expr.pre.incr]
Der Operand muss [...] oder ein Zeiger auf einen vollständig definierten Objekttyp sein.
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:
[basic.stc] 4 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.
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:
[basic.compound]
Ein gültiger Wert eines Objektzeigertyps repräsentiert entweder die Adresse eines Bytes im Speicher oder einen Nullzeiger. Befindet sich ein Objekt vom Typ T an einer Adresse, so wird A [...] auf dieses Objekt verweisen, unabhängig davon, wie der Wert erhalten wurde .
[Hinweis: Beispielsweise wird davon ausgegangen, dass die Adresse nach dem Ende eines Arrays auf ein nicht verwandtes Objekt des Elementtyps des Arrays verweist, das sich möglicherweise an dieser Adresse befindet. [...]].
Lesen Sie als: OK, wen interessiert das? Solange ein Zeiger irgendwo in der Erinnerung zeigt , bin ich gut?
[basic.stc.dynamic.safety] Ein Zeigerwert ist ein sicher abgeleiteter Zeiger [bla bla]
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.
Eine Implementierung kann die Zeigersicherheit gelockert haben. In diesem Fall hängt die Gültigkeit eines Zeigerwerts nicht davon ab, ob es sich um einen sicher abgeleiteten Zeigerwert handelt.
Oh, also ist es vielleicht egal, was ich dachte. Aber warte ... "darf nicht"? Das heißt, es kann auch . Wie soll ich wissen?
Alternativ kann eine Implementierung eine strenge Zeigersicherheit aufweisen. In diesem Fall ist ein Zeigerwert, der kein sicher abgeleiteter Zeigerwert ist, ein ungültiger Zeigerwert, es sei denn, das referenzierte vollständige Objekt hat eine dynamische Speicherdauer und wurde zuvor als erreichbar deklariert
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:
[expr.reinterpret.cast] 5
Ein Wert vom Integraltyp oder Aufzählungstyp kann explizit in einen Zeiger konvertiert werden. Ein Zeiger, der in eine Ganzzahl von ausreichender Größe [...] und zurück zum gleichen [...] ursprünglichen Wert des Zeigertyps konvertiert wurde; Zuordnungen zwischen Zeigern und Ganzzahlen sind ansonsten implementierungsdefiniert.
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.