En su ejemplo, *(p1 + 1) = 10;
debería ser UB, porque es uno más allá del final de la matriz de tamaño 1. Pero estamos en un caso muy especial aquí, porque la matriz se construyó dinámicamente en una matriz de caracteres más grande.
La creación dinámica de objetos se describe en 4.5 El modelo de objetos de C ++ [intro.object] , §3 del borrador n4659 del estándar C ++:
3 Si se crea un objeto completo (8.3.4) en el almacenamiento asociado con otro objeto e de tipo "matriz de N caracteres sin signo" o de tipo "matriz de N std :: byte" (21.2.1), esa matriz proporciona almacenamiento para el objeto creado si:
(3.1) - la vida útil de e ha comenzado y no ha terminado, y
(3.2) - el almacenamiento del nuevo objeto encaja completamente dentro de e, y
(3.3) - no hay un objeto de matriz más pequeño que satisfaga estos limitaciones.
El 3.3 parece bastante poco claro, pero los ejemplos a continuación aclaran la intención:
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;
Entonces, en el ejemplo, la buffer
matriz proporciona almacenamiento para ambos *p1
y *p2
.
Los siguientes párrafos prueban que el objeto completo para ambos *p1
y *p2
es buffer
:
4 Un objeto a está anidado dentro de otro objeto b si:
(4.1) - a es un subobjeto de b, o
(4.2) - b proporciona almacenamiento para a, o
(4.3) - existe un objeto c donde a está anidado dentro de c , y c está anidado dentro de b.
5 Para cada objeto x, hay un objeto llamado objeto completo de x, determinado de la siguiente manera:
(5.1) - Si x es un objeto completo, entonces el objeto completo de x es él mismo.
(5.2) - De lo contrario, el objeto completo de x es el objeto completo del objeto (único) que contiene x.
Una vez que esto se establece, la otra parte relevante del borrador n4659 para C ++ 17 es [basic.coumpound] §3 (enfatice el mío):
3 ... Cada valor de tipo de puntero es uno de los siguientes:
(3.1) - un puntero a un objeto o función (se dice que el puntero apunta al objeto o función), o
(3.2) - un puntero más allá del final de un objeto (8.7), o
(3.3) - el valor de puntero nulo (7.11) para ese tipo, o
(3.4) - un valor de puntero no válido.
Un valor de un tipo de puntero que es un puntero hacia o más allá del final de un objeto representa la dirección del primer byte en la memoria (4.4) ocupado por el objeto o el primer byte en la memoria después del final del almacenamiento
ocupado por el objeto , respectivamente. [Nota: Un puntero más allá del final de un objeto (8.7) no se considera que apuntan a una relaciónobjeto del tipo de objeto que podría estar ubicado en esa dirección. Un valor de puntero se vuelve inválido cuando el almacenamiento que denota llega al final de su duración de almacenamiento; ver 6.7. —Nota final] A los efectos de la aritmética de punteros (8.7) y la comparación (8.9, 8.10), un puntero más allá del final del último elemento de una matriz x de n elementos se considera equivalente a un puntero a un elemento hipotético x [ norte]. La representación del valor de los tipos de puntero está definida por la implementación. Los punteros a tipos compatibles con el diseño deben tener los mismos requisitos de alineación y representación de valores (6.11) ...
La nota Un puntero más allá del final ... no se aplica aquí porque los objetos apuntados por p1
y p2
no sin relación , pero están anidados en el mismo objeto completo, por lo que la aritmética de puntero tiene sentido dentro del objeto que proporciona almacenamiento: p2 - p1
está definido y es (&buffer[sizeof(int)] - buffer]) / sizeof(int)
eso es 1.
Por lo que p1 + 1
es un puntero a *p2
, *(p1 + 1) = 10;
tiene un comportamiento definido y establece el valor de *p2
.
También he leído el anexo C4 sobre la compatibilidad entre C ++ 14 y los estándares actuales (C ++ 17). Eliminar la posibilidad de usar aritmética de puntero entre objetos creados dinámicamente en una sola matriz de caracteres sería un cambio importante que en mi humilde opinión debería citarse allí, porque es una característica de uso común. Como no existe nada al respecto en las páginas de compatibilidad, creo que confirma que no era la intención del estándar prohibirlo.
En particular, derrotaría esa construcción dinámica común de una matriz de objetos de una clase sin un constructor predeterminado:
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
luego se puede usar como un puntero al primer elemento de una matriz ...