Jeter un pointeur struct à un autre – C

Veuillez considérer le code suivant.

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; } 

Dans la fonction cons , la variable ptr est de type cons_object* . Mais dans la valeur de retour, il est converti en type d’ object* .

  1. Je me demande comment cela est possible car cons_object et object sont des structures différentes.
  2. Y a-t-il des problèmes à faire ce genre de choses?

Des pensées!

Ceci est correct et constitue une technique assez courante pour implémenter “l’orientation object” en C. Etant donné que la disposition mémoire de struct s est bien définie en C, tant que les deux objects partagent la même disposition, vous pouvez faire basculer en toute sécurité des pointeurs entre leur. Autrement dit, le décalage du membre de type est le même dans la structure d’ object que dans la structure cons_object .

Dans ce cas, le membre de type indique à l’API si l’ object est un object cons_object ou foo_object ou un autre type d’object. Vous pouvez donc voir quelque chose comme ceci:

 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 } 

Plus généralement, j’ai l’air d’implémentations où la classe “parent” est définie comme le premier membre de la classe “enfant”, comme ceci:

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

Cela fonctionne en grande partie de la même manière, sauf que vous avez une garantie forte que la structure de mémoire des “classes” enfants sera la même que celle des parents. Autrement dit, si vous ajoutez un membre à l’ object ‘base’, il sera automatiquement repris par les enfants et vous ne devrez pas vous assurer manuellement que toutes les structures sont synchronisées.

Pour append à la réponse de Dean, voici quelque chose à propos des conversions de pointeurs en général. J’ai oublié quel est le terme pour cela, mais un pointeur vers une conversion de pointeur n’effectue aucune conversion (de la même manière que int est float). C’est simplement une réinterprétation des bits qu’ils désignent (tout pour le bénéfice du compilateur). “Conversion non destructive” Je pense que c’était. Les données ne changent pas, seulement comment le compilateur interprète ce qui est pointé.

par exemple,
Si ptr est un pointeur sur un object , le compilateur sait qu’il existe un champ avec un décalage particulier nommé type de type enum type . D’autre part, si ptr est ptr en un pointeur vers un type différent, cons_object , il saura à nouveau comment accéder aux champs de l’object cons_object chacun avec ses propres décalages de manière similaire.

Pour illustrer, imaginez la disposition de la mémoire pour un object cons_object :

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

Le champ type a le décalage 0, la car est 4, le cdr est 8. Pour accéder au champ voiture, il suffit au compilateur d’append 4 au pointeur sur la structure.

Si le pointeur a été converti en un pointeur vers un object :

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

Tout ce que le compilateur doit savoir, c’est qu’il existe un champ appelé type avec décalage 0. Tout ce qui est en mémoire est en mémoire.

Les pointeurs n’ont même pas besoin d’être liés quoi que ce soit. Vous pouvez avoir un pointeur sur un int et le cons_object en un pointeur sur un object cons_object . Si vous deviez accéder au champ de car , c’est comme n’importe quel access mémoire ordinaire. Il a un certain décalage depuis le début de la structure. Dans ce cas, le contenu de cet emplacement de mémoire est inconnu, mais cela n’a aucune importance. Pour accéder à un champ, seul le décalage est nécessaire et cette information se trouve dans la définition du type.

Un pointeur sur un int indique un bloc de mémoire:

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

Transmis à un pointeur cons_object :

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

L’utilisation de structures séparées enfreint la règle d’aliasing ssortingcte et constitue un comportement indéfini: http://cellperformance.beyond3d.com/articles/2006/06/understanding-ssortingct-aliasing.html

Utiliser une structure incorporée comme dans le dernier exemple de Dean est acceptable.