Lanzar un puntero struct a otro – C

Por favor considere el siguiente código.

enum type {CONS, ATOM, FUNC, LAMBDA}; typedef struct{ enum type type; } object; typedef struct { enum type type; object *car; object *cdr; } cons_object; object *cons (object *first, object *second) { cons_object *ptr = (cons_object *) malloc (sizeof (cons_object)); ptr->type = CONS; ptr->car = first; ptr->cdr = second; return (object *) ptr; } 

En la función cons , la variable ptr es del tipo cons_object* . Pero en el valor de retorno se convierte en tipo de object* .

  1. Me pregunto cómo es posible porque cons_object y object son estructuras diferentes.
  2. ¿Hay algún problema al hacer cosas como esta?

¡Alguna idea!

Esto está bien y es una técnica bastante común para implementar “orientación a objetos” en C. Debido a que el diseño de la memoria de las struct está bien definido en C, siempre y cuando los dos objetos compartan el mismo diseño, puedes lanzar punteros de forma segura ellos. Es decir, el desplazamiento del miembro de type es el mismo en la estructura del object que en la estructura cons_object .

En este caso, el miembro de type le dice a la API si el object es cons_object o foo_object o algún otro tipo de objeto, por lo que es posible que vea algo como esto:

 void traverse(object *obj) { if (obj->type == CONS) { cons_object *cons = (cons_object *)obj; traverse(cons->car); traverse(cons->cdr); } else if (obj->type == FOO) { foo_object *foo = (foo_object *)obj; traverse_foo(foo); } else ... etc } 

Más comúnmente, parece que las implementaciones donde la clase “padre” se define como el primer miembro de la clase “hijo”, así:

 typedef struct { enum type type; } object; typedef struct { object parent; object *car; object *cdr; } cons_object; 

Esto funciona en gran medida de la misma manera, excepto que usted tiene una garantía fuerte de que el diseño de la memoria de las “clases” de los niños será el mismo que el de los padres. Es decir, si agrega un miembro al object ‘base’, los niños lo recogerán automáticamente y no tendrá que asegurarse manualmente de que todas las estructuras estén sincronizadas.

Para agregar a la respuesta de Dean, aquí hay algo sobre las conversiones de punteros en general. Olvidé cuál es el término para esto, pero un puntero al molde de puntero no realiza ninguna conversión (del mismo modo que int float). Es simplemente una reinterpretación de los bits que señalan (todo para el beneficio del comstackdor). “Conversión no destructiva” Creo que fue. Los datos no cambian, solo cómo el comstackdor interpreta a qué se apunta.

p.ej,
Si ptr es un puntero a un object , el comstackdor sabe que hay un campo con un desplazamiento particular llamado type de tipo tipo enum type . Por otro lado, si ptr se ptr en un puntero a un tipo diferente, cons_object , nuevamente sabrá cómo acceder a los campos del cons_object cada uno con sus propios desplazamientos de forma similar.

Para ilustrar imagine el diseño de memoria para un cons_object :

  +---+---+---+---+ cons_object *ptr -> | t | y | p | e | enum type +---+---+---+---+ | c | a | r | | object * +---+---+---+---+ | c | d | r | | object * +---+---+---+---+ 

El campo de type tiene el desplazamiento 0, el car es 4, el cdr es 8. Para acceder al campo de automóvil, todo lo que el comstackdor debe hacer es agregar 4 al puntero a la estructura.

Si el puntero se convirtió en un puntero a un object :

  +---+---+---+---+ ((object *)ptr) -> | t | y | p | e | enum type +---+---+---+---+ | c | a | r | | +---+---+---+---+ | c | d | r | | +---+---+---+---+ 

Todo lo que el comstackdor necesita saber es que hay un campo llamado type con offset 0. Lo que está en la memoria está en la memoria.

Los punteros ni siquiera tienen que estar relacionados en absoluto. Puede tener un puntero a un int y convertirlo en un puntero a un cons_object . Si tuviera acceso al campo del car , es como cualquier acceso ordinario a la memoria. Tiene un cierto desplazamiento desde el comienzo de la estructura. En este caso, lo que está en esa ubicación de memoria es desconocido, pero eso no es importante. Para acceder a un campo, solo se necesita el desplazamiento y esa información se encuentra en la definición del tipo.

Un puntero a un int apunta a un bloque de memoria:

  +---+---+---+---+ int *ptr -> | i | n | t | | int +---+---+---+---+ 

Lanzado a un puntero cons_object :

  +---+---+---+---+ ((cons_object *)ptr) -> | i | n | t | | enum type +---+---+---+---+ | X | X | X | X | object * +---+---+---+---+ | X | X | X | X | object * +---+---+---+---+ 

El uso de estructuras separadas viola la regla de alias estricta y es un comportamiento indefinido: http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

Usar una estructura incrustada como en el último ejemplo de Dean está bien.