Nel tuo esempio, *(p1 + 1) = 10;
dovrebbe essere UB, perché lo è uno oltre la fine dell'array di dimensione 1. Ma qui ci troviamo in un caso molto speciale, perché l'array è stato costruito dinamicamente in un array di caratteri più grande.
La creazione di oggetti dinamici è descritta in 4.5 Il modello di oggetti C ++ [intro.object] , §3 della bozza n4659 dello standard C ++:
3 Se viene creato un oggetto completo (8.3.4) nella memoria associata a un altro oggetto e di tipo "matrice di N caratteri senza segno" o di tipo "matrice di N std :: byte" (21.2.1), tale matrice fornisce per l'oggetto creato se:
(3.1) - la durata di e è iniziata e non terminata, e
(3.2) - la memoria per il nuovo oggetto rientra interamente in e, e
(3.3) - non esiste un oggetto array più piccolo che soddisfi questi vincoli.
Il 3.3 sembra piuttosto poco chiaro, ma gli esempi seguenti rendono più chiaro l'intento:
struct A { unsigned char a[32]; };
struct B { unsigned char b[16]; };
A a;
B *b = new (a.a + 8) B;
int *p = new (b->b + 4) int;
Quindi, nell'esempio, l' buffer
array fornisce spazio di archiviazione per entrambi *p1
e *p2
.
I paragrafi seguenti dimostrano che l'oggetto completo per entrambi *p1
e *p2
èbuffer
:
4 Un oggetto a è annidato all'interno di un altro oggetto b se:
(4.1) - a è un sottooggetto di b, o
(4.2) - b fornisce memoria per a, o
(4.3) - esiste un oggetto c dove a è annidato all'interno di c e c è annidato in b.
5 Per ogni oggetto x, c'è un oggetto chiamato oggetto completo di x, determinato come segue:
(5.1) - Se x è un oggetto completo, allora l'oggetto completo di x è esso stesso.
(5.2) - Altrimenti, l'oggetto completo di x è l'oggetto completo dell'oggetto (unico) che contiene x.
Una volta stabilito ciò, l'altra parte rilevante della bozza n4659 per C ++ 17 è [basic.coumpound] §3 (enfatizza la mia):
3 ... Ogni valore del tipo di puntatore è uno dei seguenti:
(3.1) - un puntatore a un oggetto o una funzione (si dice che il puntatore punti all'oggetto o alla funzione), o
(3.2) - un puntatore oltre la fine di un oggetto (8.7), o
(3.3) - il valore del puntatore nullo (7.11) per quel tipo, o
(3.4) - un valore del puntatore non valido.
Un valore di un tipo di puntatore che è un puntatore alla o oltre la fine di un oggetto rappresenta l'indirizzo del primo byte in memoria (4.4) occupato dall'oggetto o il primo byte in memoria dopo la fine della memoria
occupata dall'oggetto , rispettivamente. [Nota: un puntatore oltre la fine di un oggetto (8.7) non è considerato puntare a un non correlatooggetto del tipo di oggetto che potrebbe trovarsi a quell'indirizzo. Il valore di un puntatore diventa non valido quando la memoria che indica raggiunge la fine della sua durata di memorizzazione; vedere 6.7. —End note] Ai fini dell'aritmetica dei puntatori (8.7) e del confronto (8.9, 8.10), un puntatore oltre la fine dell'ultimo elemento di un array x di n elementi è considerato equivalente a un puntatore a un ipotetico elemento x [ n]. La rappresentazione del valore dei tipi di puntatore è definita dall'implementazione. I puntatori a tipi compatibili con il layout devono avere gli stessi requisiti di rappresentazione del valore e allineamento (6.11) ...
La nota Un puntatore oltre la fine ... non si applica qui perché gli oggetti puntati da p1
e p2
non sono estranei , ma sono annidati nello stesso oggetto completo, quindi l'aritmetica dei puntatori ha senso all'interno dell'oggetto che fornisce memoria: p2 - p1
è definito ed è (&buffer[sizeof(int)] - buffer]) / sizeof(int)
questo è 1.
Quindi p1 + 1
è un puntatore a *p2
, *(p1 + 1) = 10;
ha un comportamento definito e imposta il valore di *p2
.
Ho anche letto l'allegato C4 sulla compatibilità tra C ++ 14 e gli standard attuali (C ++ 17). Rimuovere la possibilità di utilizzare l'aritmetica del puntatore tra oggetti creati dinamicamente in un singolo array di caratteri sarebbe un cambiamento importante che IMHO dovrebbe essere citato lì, perché è una caratteristica comunemente usata. Poiché non esiste nulla al riguardo nelle pagine di compatibilità, penso che confermi che non era l'intento dello standard vietarlo.
In particolare, sconfiggerebbe quella comune costruzione dinamica di un array di oggetti da una classe senza costruttore predefinito:
class T {
...
public T(U initialization) {
...
}
};
...
unsigned char *mem = new unsigned char[N * sizeof(T)];
T * arr = reinterpret_cast<T*>(mem);
for (i=0; i<N; i++) {
U u(...);
new(arr + i) T(u);
}
arr
può quindi essere utilizzato come puntatore al primo elemento di un array ...