(En referencia a esta pregunta y respuesta ).
Antes del estándar C ++ 17, la siguiente oración se incluía en [basic.compound] / 3 :
Si un objeto de tipo T está ubicado en una dirección A, se dice que un puntero de tipo cv T * cuyo valor es la dirección A apunta a ese objeto, independientemente de cómo se obtuvo el valor.
Pero desde C ++ 17, esta oración se ha eliminado .
Por ejemplo, creo que esta oración hizo que este código de ejemplo se definiera, y que desde C ++ 17 este es un comportamiento indefinido:
alignas(int) unsigned char buffer[2*sizeof(int)];
auto p1=new(buffer) int{};
auto p2=new(p1+1) int{};
*(p1+1)=10;
Antes de C ++ 17, p1+1
contiene la dirección ay *p2
tiene el tipo correcto, por lo que *(p1+1)
es un puntero a *p2
. En C ++ 17 p1+1
es un puntero más allá del final , por lo que no es un puntero al objeto y creo que no es desreferenciable.
¿Es esta interpretación de esta modificación del derecho estándar o existen otras reglas que compensen la supresión de la frase citada?
int a, b = 0;
, no puede hacer*(&a + 1) = 1;
incluso si marcó&a + 1 == &b
. Si puede obtener un puntero válido a un objeto simplemente adivinando su dirección, incluso almacenar variables locales en registros se vuelve problemático.Respuestas:
Sí, esta interpretación es correcta. Un puntero más allá del final no es simplemente convertible en otro valor de puntero que apunte a esa dirección.
El nuevo [basic.compound] / 3 dice:
Esos son mutuamente excluyentes.
p1+1
es un puntero más allá del final, no un puntero a un objeto.p1+1
apunta a un hipotéticox[1]
de una matriz de tamaño 1 enp1
, no ap2
. Estos dos objetos no son interconvertibles por puntero.También tenemos la nota no normativa:
que aclara la intención.
Como TC señala en numerosos comentarios (en particular este ), este es realmente un caso especial del problema que surge al intentar implementar
std::vector
, que es que[v.data(), v.data() + v.size())
debe ser un rango válido y, sinvector
embargo, no crea un objeto de matriz, por lo que el solo la aritmética de puntero definida iría desde cualquier objeto dado en el vector hasta más allá del final de su matriz hipotética de un tamaño. Para obtener más recursos, consulte CWG 2182 , esta discusión estándar y dos revisiones de un documento sobre el tema: P0593R0 y P0593R1 (sección 1.3 específicamente).fuente
vector
problema de implementabilidad". +1.p1+1
ya no produciría un puntero más allá del final y toda la discusión sobre los punteros más allá del final es discutible. Su caso especial particular de dos elementos puede no ser UB pre-17, pero tampoco es muy interesante.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 ++:
El 3.3 parece bastante poco claro, pero los ejemplos a continuación aclaran la intención:
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
esbuffer
:Una vez que esto se establece, la otra parte relevante del borrador n4659 para C ++ 17 es [basic.coumpound] §3 (enfatice el mío):
La nota Un puntero más allá del final ... no se aplica aquí porque los objetos apuntados por
p1
yp2
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); // See the array as an array of N T 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 ...fuente
vector
no crea (y no puede) un objeto de matriz, pero tiene una interfaz que permite al usuario obtener un puntero que admita la aritmética de punteros (que solo se define para punteros en objetos de matriz).Para ampliar las respuestas que se dan aquí, hay un ejemplo de lo que creo que excluye la redacción revisada:
Advertencia: comportamiento indefinido
#include <iostream> int main() { int A[1]{7}; int B[1]{10}; bool same{(B)==(A+1)}; std::cout<<B<< ' '<< A <<' '<<sizeof(*A)<<'\n'; std::cout<<(same?"same":"not same")<<'\n'; std::cout<<*(A+1)<<'\n';//!!!!! return 0; }
Por razones totalmente dependientes de la implementación (y frágiles), el resultado posible de este programa es:
0x7fff1e4f2a64 0x7fff1e4f2a60 4 same 10
Esa salida muestra que las dos matrices (en ese caso) se almacenan en la memoria de manera que 'uno más allá del final' de
A
tiene el valor de la dirección del primer elemento deB
.La especificación revisada asegura que independientemente
A+1
nunca sea un puntero válidoB
. La antigua frase 'independientemente de cómo se obtenga el valor' dice que si 'A + 1' apunta a 'B [0]', entonces es un puntero válido a 'B [0]'. Eso no puede ser bueno y seguramente nunca la intención.fuente
-O0
en algunos compiladores) no considera a los punteros como tipos triviales. Los compiladores no se toman en serio los requisitos del std, ni tampoco las personas que escriben el std, que sueñan con un lenguaje diferente y hacen todo tipo de inventos que contradicen directamente principios básicos. Obviamente, los usuarios se confunden y, a veces, se les trata mal cuando se quejan de errores del compilador.