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*
.
cons_object
et object
sont des structures différentes. 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.