c++ arrays pointers language-lawyer c++17

c++ - Interconvertibilidad del puntero vs tener la misma dirección.



arrays pointers (4)

El borrador de trabajo de la norma N4659 dice:

[basic.compound]
Si dos objetos son interconvertibles con el puntero, entonces tienen la misma dirección

y luego nota que

Un objeto de matriz y su primer elemento no son punteros interconvertibles, a pesar de que tienen la misma dirección

¿Cuál es la razón para hacer que un objeto de matriz y su primer elemento no sean interconvertibles con el puntero? De manera más general, ¿cuál es la razón para distinguir la noción de interconvertibilidad de puntero de la noción de tener la misma dirección? ¿No hay una contradicción en alguna parte?

Parece que dada esta secuencia de afirmaciones

int a[10]; void* p1 = static_cast<void*>(&a[0]); void* p2 = static_cast<void*>(&a); int* i1 = static_cast<int*>(p1); int* i2 = static_cast<int*>(p2);

tenemos p1 == p2 , sin embargo, i1 está bien definido y usar i2 resultaría en UB.


De manera más general, ¿cuál es la razón para distinguir la noción de interconvertibilidad de puntero de la noción de tener la misma dirección?

Es difícil, si no imposible, responder por qué ciertas decisiones se toman de acuerdo con el estándar, pero esta es mi opinión.

Lógicamente, los punteros apuntan a objetos , no a direcciones. Las direcciones son las representaciones de valor de los punteros. La distinción es particularmente importante cuando se reutiliza el espacio de un objeto que contiene miembros const

struct S { const int i; }; S s = {42}; auto ps = &s; new (ps) S{420}; foo(ps->i); // UB, requires std::launder

Un puntero con la misma representación de valor se puede usar como si fuera el mismo puntero se debe considerar como el caso especial en lugar de al revés.

En la práctica, el estándar intenta colocar la menor restricción posible en las implementaciones. La interconvertibilidad del puntero es la condición de que los punteros puedan reinterpret_cast y generar el resultado correcto. Al ver cómo se pretende que reinterpret_cast se compile en nada, también significa que los punteros comparten la misma representación de valor . Dado que eso coloca más restricciones en las implementaciones, la condición no se dará sin razones convincentes.


Aparentemente existen implementaciones que optimizan en función de esto. Considerar:

struct A { double x[4]; int n; }; void g(double* p); int f() { A a { {}, 42 }; g(&a.x[1]); return a.n; // optimized to return 42; // valid only if you can''t validly obtain &a.n from &a.x[1] }

Dado p = &a.x[1]; , g podría intentar obtener acceso a an reinterpret_cast<A*>(reinterpret_cast<double(*)[4]>(p - 1))->n . Si el lanzamiento interno produjo con éxito un puntero a ax , entonces el lanzamiento externo arrojará un indicador a, lo que dará al miembro de la clase un comportamiento definido de acceso y, por lo tanto, prohibirá la optimización.


Debido a que el comité desea dejar en claro que una matriz es un concepto de bajo nivel, no es un objeto de primera clase: no puede devolver una matriz ni asignársela, por ejemplo. La interconvertibilidad del puntero es un concepto entre objetos del mismo nivel : solo clases de diseño estándar o uniones.

El concepto rara vez se usa en todo el borrador: en [expr.static.cast] donde aparece como un caso especial, en [class.mem] donde una nota dice que para las clases de diseño estándar, los indicadores de un objeto y su primer subobjeto son interconvertible, en [class.union] donde los punteros a la unión y sus miembros de datos no estáticos también se declaran interconvertibles y en [ptr.launder].

La última aparición separa 2 casos de uso: o los punteros son interconvertibles, o un elemento es una matriz. Esto se afirma en un comentario y no en una nota como está en [basic.compound], por lo que deja más claro que el puntero-interconvertibilidad no se refiere a matrices.


Un objeto de matriz y su primer elemento no son punteros interconvertibles, pero std::launder debería permitir obtener uno del otro en algunos casos .

La siguiente información está tachada hasta que esté seguro de que es correcta.

Aunque reinterpret_cast de "puntero a un tipo de elemento de matriz" T* a "puntero a tipo de matriz" T(*)[N] no le dará un puntero al objeto de matriz, puede std::launder el resultado de reinterpret_cast y obtener el puntero al objeto de matriz (si existe, por supuesto). Se puede acceder a todos los bytes del objeto de matriz que contiene inmediatamente un elemento de matriz a través del puntero al elemento de matriz. (vea http://eel.is/c++draft/ptr.launder ).

Como consecuencia, la respuesta actualmente aceptada es incorrecta. El compilador no puede optimizar para return 42 .

Dado p0 = &a.x[1] , p1 = p0 - 1 apunta a ax[0] , p2 = std::launder(reinterpret_cast<double(*)[4]>(p1)) apunta a ax , p3 = reinterpret_cast<A*>(p2) p3->n = 88 p3 = reinterpret_cast<A*>(p2) apunta a a , p3->n = 88 cambia an y el return 42 sería una optimización incorrecta.