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:
3 Wenn ein vollständiges Objekt (8.3.4) im Speicher erstellt wird, der einem anderen Objekt e vom Typ "Array von N vorzeichenlosem Zeichen" oder vom Typ "Array von N std :: byte" (21.2.1) zugeordnet ist, stellt dieses Array Speicher bereit für das erstellte Objekt, wenn:
(3.1) - die Lebensdauer von e begonnen und nicht beendet wurde und
(3.2) - der Speicher für das neue Objekt vollständig in e passt und
(3.3) - es kein kleineres Array-Objekt gibt, das diese erfüllt Einschränkungen.
Die 3.3 scheint ziemlich unklar, aber die folgenden Beispiele machen die Absicht klarer:
struct A { unsigned char a[32]; };
struct B { unsigned char b[16]; };
A a;
B *b = new (a.a + 8) B; // a.a provides storage for *b
int *p = new (b->b + 4) int; // b->b provides storage for *p
// a.a does not provide storage for *p (directly),
// but *p is nested within a (see below)
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
ist buffer
:
4 Ein Objekt a ist in einem anderen Objekt b verschachtelt, wenn:
(4.1) - a ein Unterobjekt von b ist oder
(4.2) - b Speicher für a bereitstellt oder
(4.3) - es ein Objekt c gibt, in dem a in c verschachtelt ist und c ist in b verschachtelt.
5 Für jedes Objekt x gibt es ein Objekt, das als vollständiges Objekt von x bezeichnet wird und wie folgt bestimmt wird:
(5.1) - Wenn x ein vollständiges Objekt ist, ist das vollständige Objekt von x selbst.
(5.2) - Andernfalls ist das vollständige Objekt von x das vollständige Objekt des (eindeutigen) Objekts, das x enthält.
Sobald dies festgelegt ist, ist der andere relevante Teil des Entwurfs n4659 für C ++ 17 [basic.coumpound] §3 (betonen Sie meinen):
3 ... Jeder Wert des Zeigertyps ist einer der folgenden:
(3.1) - ein Zeiger auf ein Objekt oder eine Funktion (der Zeiger soll auf das Objekt oder die Funktion zeigen) oder
(3.2) - ein Zeiger nach dem Ende eines Objekts (8.7) oder
(3.3) - der Nullzeigerwert (7.11) für diesen Typ oder
(3.4) - ein ungültiger Zeigerwert.
Ein Wert eines Zeigertyps, der ein Zeiger auf oder hinter das Ende eines Objekts ist, repräsentiert die Adresse des ersten Bytes im Speicher (4.4), das vom Objekt belegt ist, oder des ersten Bytes im Speicher nach dem Ende des
vom Objekt belegten Speichers , beziehungsweise. [Hinweis: Ein Zeiger hinter dem Ende eines Objekts (8.7) zeigt nicht auf ein nicht verwandtes ObjektObjekt des Objekttyps, das sich möglicherweise an dieser Adresse befindet. Ein Zeigerwert wird ungültig, wenn der von ihm angegebene Speicher das Ende seiner Speicherdauer erreicht. siehe 6.7. —End note] Für die Zeigerarithmetik (8.7) und den Vergleich (8.9, 8.10) wird ein Zeiger nach dem Ende des letzten Elements eines Arrays x von n Elementen als äquivalent zu einem Zeiger auf ein hypothetisches Element x [angesehen. n]. Die Wertdarstellung von Zeigertypen ist implementierungsdefiniert. Zeiger auf layoutkompatible Typen müssen dieselben Anforderungen an die Wertdarstellung und Ausrichtung haben (6.11) ...
Der Hinweis Ein Zeiger nach dem Ende ... gilt hier nicht, da die Objekte, auf die durch p1
und p2
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:
class T {
...
public T(U initialization) {
...
}
};
...
unsigned char *mem = new unsigned char[N * sizeof(T)];
T * arr = reinterpret_cast<T*>(mem); // See the array as an array of N T
for (i=0; i<N; i++) {
U u(...);
new(arr + i) T(u);
}
arr
kann dann als Zeiger auf das erste Element eines Arrays verwendet werden ...