¿Es posible obtener un puntero a un subobjeto a través de un puntero a un subobjeto diferente no relacionado?

4 minutos de lectura

avatar de usuario
gueza

Mira este sencillo código:

struct Point {
    int x;
    int y;
};

void something(int *);

int main() {
    Point p{1, 2};

    something(&p.x);

    return p.y;
}

Eso espero mainEl valor de retorno de se puede optimizar para return 2;como something no tiene acceso a p.ysolo recibe un puntero a p.x.

Pero, ninguno de los principales compiladores optimiza el valor de retorno de main a 2. Rayo de Dios.

¿Hay algo en el estándar que permita something Modificar p.ysi solo damos acceso a p.x? En caso afirmativo, ¿depende esto de si Point tiene diseño estándar?

¿Qué pasa si uso something(&p.y);y return p.x; ¿en cambio?

  • No entiendo el voto negativo y la bandera. Esta es una pregunta perfectamente razonable para mí.

    – Zereges

    17 sep 2019 a las 17:13

  • stackoverflow.com/questions/50803202/… “Los requisitos de alineación de la implementación pueden hacer que dos miembros adyacentes no se asignen inmediatamente uno detrás del otro”

    – alter_igel

    17 sep 2019 a las 17:13

  • @alterigel: esto no debería importar, ya que podemos usar offsetof. Pero no estoy seguro de que podamos obtener un puntero para p.y de p.xcon las nuevas reglas semánticas de punteros de C++17. std::launder no se puede utilizar, como un puntero a p.x solo puede alcanzar p.x. Entonces, incluso si pudiera mover el puntero a la ubicación correcta, tal vez ese puntero no apunte al objeto de p.y.

    – geza

    17 sep 2019 a las 17:17


  • Si lo cambias a void something(int * x) { *(x+1) = 6; } optimiza el valor de retorno a un valor fijo 6por lo que el compilador al menos ve eso como una opción válida para hacer.

    – t.niese

    17 sep 2019 a las 17:20

  • @zereges, no voté en contra, pero casi lo hice. La pregunta es muy difícil de analizar. Creo que el OP pregunta: “¿por qué el compilador no puede darse cuenta de que la función something () no modificará py?” Si el OP preguntara eso directamente, habría menos votos negativos.

    – Jeffrey

    17 sep 2019 a las 17:26


avatar de usuario
barry

Esto está perfectamente bien definido:

void something(int *x) {
    reinterpret_cast<Point*>(x)->y = 42;
}

los Point objeto (p) y es x miembro son puntero interconvertible, de [basic.compound]:

dos objetos a y b son puntero-interconvertible si:

  • […]
  • uno es un objeto de clase de diseño estándar y el otro es el primer miembro de datos no estáticos de ese objeto o, si el objeto no tiene miembros de datos no estáticos, cualquier subobjeto de clase base de ese objeto ([class.mem]), o:
  • […]

Si dos objetos son punteros interconvertibles, entonces tienen la misma dirección y es posible obtener un puntero a uno de un puntero al otro a través de un reinterpret_­cast.

Que reinterpret_cast<Point*>(x) es válido y termina con un puntero que apunta a p. Por lo tanto, modificarlo directamente está bien. Como puede ver, la parte de diseño estándar y la primera parte del miembro de datos no estáticos son significativas.


Aunque no es que los compiladores en cuestión optimicen la carga adicional si pasa un puntero a p.y entrar y volver p.x en cambio.

  • Gracias, sabía que dejé un hueco en mi pregunta. Entonces, si usé something(&p.y); return p.x;entonces es claramente una optimización perdida?

    – geza

    17 sep 2019 a las 17:24

  • @geza Técnicamente, sí, sería una optimización perdida entonces, pero no creo que los principales compiladores, en este momento, estén preparados para romper todo el código existente que usa offsetof.

    – Brian Bi

    17 sep 2019 a las 17:29

  • ¿Eso no viola la regla de alias estricto? ¿O es una regla de solo C?

    – stucotte06

    17/09/2019 a las 17:40


  • @ sturcotte06 no lo hace por Si dos objetos son punteros interconvertibles, entonces tienen la misma dirección y es posible obtener un puntero a uno de un puntero al otro a través de reinterpret_cast.

    – NathanOliver

    17/09/2019 a las 17:40

  • ¿Me estoy perdiendo de algo? el elenco cambia el puntero a p.x en un puntero a Pointpero no obstante, sigue apuntando a la misma ubicación de memoria? Eso significaría que esto solo funciona porque x es el primer miembro y, por lo tanto, existe en la misma ubicación de memoria que el objeto ‘principal’ Point p? Así que si tuviéramos que pasar &p.y en la función, obtendríamos un comportamiento diferente (¿indefinido?)

    – Baldrickk

    18 de septiembre de 2019 a las 9:06

¿Ha sido útil esta solución?