Ich dachte, dass das Hinzufügen eines ganzzahligen Typs zu einem Zeiger (vorausgesetzt, dass der Zeiger auf ein Array mit einer bestimmten Größe usw. zeigt) immer gut definiert ist, unabhängig vom Integraltyp. Der C ++ 11 Standard sagt ([expr.add]):
Wenn ein Ausdruck, der einen ganzzahligen Typ hat, zu einem Zeiger addiert oder von ihm subtrahiert wird, hat das Ergebnis den Typ des Zeigeroperanden. Wenn der Zeigeroperand auf ein Element eines Feldobjekts zeigt und das Feld groß genug ist, zeigt das Ergebnis auf ein Element, das vom ursprünglichen Element versetzt ist, so dass die Differenz der Indizes der resultierenden und ursprünglichen Feldelemente gleich dem Integralausdruck ist. Mit anderen Worten, wenn der Ausdruck P auf das i-te Element eines Array-Objekts zeigt, zeigen die Ausdrücke (P) + N (äquivalent N + (P)) und (P) -N (wobei N den Wert n hat) um jeweils die i + n-ten und i - n-ten Elemente des Array-Objekts, sofern sie existieren. Wenn der Ausdruck P auf das letzte Element eines Array-Objekts zeigt, verweist der Ausdruck (P) +1 außerdem auf das letzte Element des Array-Objekts, und wenn der Ausdruck Q auf das letzte Element eines Array-Objekts zeigt, Der Ausdruck (Q) -1 zeigt auf das letzte Element des Array-Objekts. Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, darf die Auswertung keinen Überlauf erzeugen. Andernfalls ist das Verhalten nicht definiert.
Auf der anderen Seite wurde mir kürzlich mitgeteilt, dass die eingebauten Add-Operatoren für Zeiger in ptrdiff_t
definiert sind, was ein signierter Typ ist (siehe 13.6 / 13). Dies scheint darauf hinzuweisen, dass, wenn man eine malloc()
mit einer sehr großen (vorzeichenlosen) Größe macht und dann versucht, das Ende des zugewiesenen Speicherplatzes über eine Zeigeraddition mit einem std::size_t
-Wert zu erreichen, dies zu undefiniertem Verhalten führen kann, weil unsigned std::size_t
wird in ptrdiff_t
konvertiert, was möglicherweise UB ist.
Ich stelle mir vor, dass ähnliche Probleme auftreten würden, z. B. in operator[]()
von std::vector
, was in Form einer unsignierten size_type
implementiert wird. Im Allgemeinen erscheint es mir so, als würde es praktisch unmöglich werden, den auf einer Plattform verfügbaren Speicher vollständig zu nutzen.
Es ist erwähnenswert, dass sich weder GCC noch Clang über Ganzzahlenkonvertierungen mit Vorzeichen und Vorzeichen beklagen, bei denen alle relevanten Diagnosefunktionen aktiviert sind, wenn Pointer mit vorzeichenlosen Werten versehen werden.
Vermisse ich etwas?
BEARBEITEN : Ich möchte klarstellen, dass ich über Zusätze mit einem Zeiger und einem ganzzahligen Typ (nicht zwei Zeiger) spreche.
EDIT2 : Eine ähnliche Art, die Frage zu formulieren, könnte dies sein. Ergibt dieser Code UB in der zweiten Zeile, wenn ptrdiff_t
einen kleineren positiven Bereich hat als size_t
?
Ihre Frage basiert auf einer falschen Prämisse.
Die Subtraktion von Zeigern erzeugt ein ptrdiff_t § [expr.add] / 6:
Wenn zwei Zeiger auf Elemente desselben Array-Objekts subtrahiert werden, ist das Ergebnis die Differenz der Indizes der beiden Array-Elemente. Der Typ des Ergebnisses ist ein implementierungsdefinierter vorzeichenbehafteter Ganzzahltyp; Dieser Typ muss vom selben Typ sein, der in der Kopfzeile (18.2) als std :: ptrdiff_t definiert ist.
Das bedeutet nicht bedeutet jedoch, dass die Addition in ptrdiff_t
definiert ist. Vielmehr wird für die Addition nur eine Konvertierung angegeben (§ [expr.add] / 1):
Die üblichen arithmetischen Umwandlungen werden für Operanden des arithmetischen Typs oder des Aufzählungstyps durchgeführt.
Die "üblichen arithmetischen Umrechnungen" sind in § [expr] / 10 definiert. Dies beinhaltet nur eine Konvertierung von einem vorzeichenlosen Typ in einen signierten Typ:
Andernfalls, wenn der Typ des Operanden mit vorzeichenbehafteten Integer-Typ alle Werte vom Typ des Operanden mit vorzeichenloser Integer-Zahl darstellen kann, wird der Operand mit vorzeichenloser Ganzzahl in den Typ des Operanden mit vorzeichenbehafteter Ganzzahl konvertiert Geben Sie ein.
Also, auch wenn es einen gewissen Raum für die Frage gibt, zu welchem Typ size_t
konvertiert werden soll (und ob es überhaupt konvertiert wird), gibt es keine Frage zu einem Punkt: Die einzige Art, wie es in ein umgewandelt werden kann ptrdiff_t
ist, wenn alle seine Werte ohne Änderung als ptrdiff_t
dargestellt werden können.
Also, gegeben:
%Vor% ... der Ausdruck p + N
wird nie fehlschlagen wegen einer (vorgestellten) Konvertierung von N
in ein ptrdiff_t
, bevor die Addition stattfindet.
Da §13.6 erwähnt wird, ist es vielleicht am besten, zu sichern und genau zu prüfen, was §13.6 wirklich ist:
In dieser Unterklausel sind die möglichen Operatorfunktionen angegeben, die die in Abschnitt 5 definierten integrierten Operatoren darstellen. Diese Kandidatenfunktionen nehmen am Operator-Overload-Resolution-Prozess teil, wie in 13.3.1.2 beschrieben, und werden für keinen anderen Zweck verwendet .
[Hervorhebung hinzugefügt]
Mit anderen Worten, die Tatsache, dass §13.6 einen Operator definiert, der einem Zeiger ein ptrdiff_t
hinzufügt, bedeutet nicht , dass, wenn ein anderer Integer-Typ zu einem Zeiger hinzugefügt wird, dieser zuerst konvertiert wird ein ptrdiff_t, oder etwas ähnliches. Im Allgemeinen werden die in §13.6 definierten Operatoren niemals zur Ausführung arithmetischer Operationen verwendet.
Mit diesem und dem Rest des Textes, den Sie aus § [expr.add] zitiert haben, können wir schnell zu dem Schluss kommen, dass das Hinzufügen eines size_t
zu einem Zeiger nur dann und nur dann überlaufen kann, wenn es nicht so viele Elemente gibt Array nach dem Zeiger.
Angesichts der oben genannten, tritt wahrscheinlich eine weitere Frage für Sie auf. Wenn ich Code wie folgt habe:
%Vor%... ist es möglich, dass die letzte Subtraktion überläuft? Die kurze und einfache Antwort darauf lautet: Ja, kann es.