C Structure typedef avec les déclarations en aval

J’utilisais typedef pour les structures partout dans mon application. J’ai ensuite commencé à refactoriser plusieurs fichiers d’en-tête lorsqu’il a commencé à devenir maladroit. J’ai remarqué que j’avais besoin de déclarer Object, et Klass. Eh bien, à ma grande surprise, je ne pouvais pas déclarer Object ou Klass. En effet, comme vous pouvez le constater dans la structure Object et Klass, j’utilise un typedef Object et Klass.

//Klass.h typedef struct Klass Klass_t; struct Klass { void (*_initialize)(Object_t* object, Klass_t* klass); }; //Object.h typedef struct Object Object_t; struct Object { Klass_t* _klass; }; 

Au début, utiliser Typedef était génial. Mais en essayant de transmettre déclarer Object:

 struct Object_t; 

Ne fonctionne pas car il me faudrait réécrire par les déclarations de fonction comme:

 void (*_initialize)(struct Object_t* object, Klass_t* klass); 

J’ai donc décidé de simplement taper Object dans le fichier d’en-tête Klass.h:

 typedef struct Object Object_t; 

Eh bien, lorsque tous les fichiers d’en-tête sont inclus dans mon fichier Main.c, cela complian:

 Object.h:5: error: redefinition of typedef 'Object_t' 

Alors, j’ai alors décidé de simplement supprimer toutes les struct typedefs et de déclarer explicitement mes structures.

Existe-t-il un moyen de typer une structure et d’envoyer une déclaration dans un autre fichier sans utiliser explicitement struct Object?

Je veux conserver la structure typedefs à l’intérieur du fichier d’en-tête où la structure est déclarée. Si je dois regrouper toutes les typedef dans un fichier d’en-tête, je préférerais ne pas utiliser typedef du tout. Quoi qu’il en soit, merci pour votre temps.

Rappelez-vous qu’une typedef est simplement un nom alternatif pour un type. Il y a une raison pour laquelle le kernel Linux n’utilise pas typedef s pour les types de structure, et vous le démontrez.

Votre problème peut être résolu de plusieurs manières. Je remarque que C11 autorise plusieurs occurrences du même typedef , mais je suppose que vous êtes coincé avec un compilateur plus ancien qui ne le supporte pas.

TL; DR

Même si le kernel Linux n’utilise pas de typedef , j’utilise généralement typedef , mais j’évite surtout les types de structure mutuellement référentiels (je ne peux penser à aucun code dans lequel j’ai utilisé un tel type). Et, comme le note Jens Gustedt dans sa réponse , j’utilise presque toujours les notations:

 typedef struct SomeTag SomeTag; 

de sorte que le nom du type et la balise de structure soient identiques (ils se trouvent dans des espaces de noms différents). Cette opération n’est pas nécessaire en C ++. lorsque vous définissez la struct SomeTag ou la class SomeTag , le nom SomeTag devient un nom de type sans nécessiter de typedef explicite (même si un typedef ne nuit pas à révéler que l’auteur est plus expérimenté en C que C ++, ou le code créé en C code).

J’observe également qu’il vaut mieux traiter les noms commençant par un trait de soulignement comme “réservés à la mise en oeuvre”. Les règles sont un peu plus complexes que cela, mais vous courez le risque que l’implémentation usurpe vos noms – et que vous ayez le droit d’usurper vos noms – lorsque vous utilisez des noms commençant par un trait de soulignement, alors ne le faites pas. De même, POSIX réserve les noms de types se terminant par _t pour la mise en œuvre si vous incluez des en-têtes POSIX (tels que lorsque vous ne comstackz pas en mode ssortingct C uniquement). Évitez de créer de tels noms. ils te feront mal tôt ou tard. (Je n’ai pas corrigé le code ci-dessous pour traiter l’un ou l’autre de ces problèmes: caveat emptor !).

Dans les fragments de code ci-dessous, j’ignore principalement le code empêchant les inclusions multiples (mais vous devriez l’avoir dans votre code).

En-tête supplémentaire

typedefs.h:

 typedef struct Object Object_t; typedef struct Klass Klass_t; 

klass.h

 #include "typedefs.h" struct Klass { void (*_initialize)(Object_t *object, Klass_t *klass); }; 

object.h

 #include "typedefs.h" struct Object { Klass_t *_klass; }; 

Cela fonctionne parce que les deux noms de types Klass_t et Object_t sont déclarés avant leur utilisation.

Utiliser struct Object dans un prototype

klass.h

 typedef struct Klass Klass_t; struct Object; struct Klass { void (*_initialize)(struct Object *object, Klass_t *klass); }; 

Ou, par souci de cohérence, il pourrait même utiliser:

  void (*_initialize)(struct Object *object, struct Klass *klass); 

object.h

 #include "klass.h" struct Object { Klass_t *_klass; }; 

Cela fonctionne parce que (dans de larges limites – fondamentalement, si les types sont définis dans la scope du fichier, pas dans une fonction), struct Object fait toujours référence au même type, que tous les détails soient ou non complètement définis.

GCC 4.8.2

Sous tous -std=c89 , -std=c99 et -std=c11 , GCC 4.8.2 accepte les typedef s répliqués, comme dans le code ci-dessous. Il faut -std=c89 -pedantic ou -std=c99 -pedantic pour obtenir des erreurs sur le typedef s répété.

Même sans l’option -pedantic , GCC 4.5.2 rejette ce code; Cependant, GCC 4.6.0 et les versions ultérieures l’acceptent sans l’option -pedantic .

klass.h

 #ifndef KLASS_H_INCLUDED #define KLASS_H_INCLUDED typedef struct Klass Klass_t; typedef struct Object Object_t; struct Klass { void (*_initialize)(Object_t *object, Klass_t *klass); }; #endif /* KLASS_H_INCLUDED */ 

object.h

 #ifndef OBJECT_H_INCLUDED #define OBJECT_H_INCLUDED typedef struct Klass Klass_t; typedef struct Object Object_t; struct Object { Klass_t *klass; }; #endif /* OBJECT_H_INCLUDED */ 

consommateur.c

 #include "klass.h" #include "object.h" Klass_t k; Object_t o; 

Vous devrez décider si vous êtes prêt à courir un risque pour votre code – quelle est l’importance de la portabilité et quelles versions de C (et quels compilateurs C) doivent-elles être portables?

Il vous manque le mot struct clé struct . CA devrait etre

 typedef struct Object Object_t; 

La déclaration anticipée de cette manière (mais voir ci-dessous) devrait toujours être possible. Ce forward déclare l’identifiant de typedef et la balise struct en même temps.

Il suffit de mettre de telles déclarations en avant de toutes vos struct avant les vraies déclarations. Tant que vous n’utilisez que des pointeurs sur ces struct dans les déclarations, tout devrait bien se passer.

nitpick: les noms avec _t sont réservés par POSIX. Cela signifie que vous devriez l’éviter, car un jour sur une plate-forme, un Object_t pourrait être prédéfini et Object_t conflit avec votre type.

Personnellement, je préfère la convention suivante

 typedef struct Object Object; 

si bien que le mot Object, avec ou sans struct , se réfère toujours au même.