Pourquoi devrions-nous typer une structure si souvent dans C?

J’ai vu beaucoup de programmes composés de structures comme celle ci-dessous

typedef struct { int i; char k; } elem; elem user; 

Pourquoi est-ce si souvent nécessaire? Toute raison spécifique ou domaine applicable?

Comme Greg Hewgill l’a dit, le typedef signifie que vous n’avez plus besoin d’écrire des struct partout. Cela permet non seulement d’économiser les frappes, mais aussi de rendre le code plus propre puisqu’il fournit une plus grande quantité d’abstraction.

Choses comme

 typedef struct { int x, y; } Point; Point point_new(int x, int y) { Point a; ax = x; ay = y; return a; } 

devient plus propre lorsque vous n’avez pas besoin de voir le mot-clé “struct” partout, il ressemble plus à un type appelé “Point” dans votre langue. Lequel, après le typedef , est le cas je suppose.

Notez également que si votre exemple (et le mien) a omis de nommer la struct elle struct même, le nommer est également utile lorsque vous souhaitez fournir un type opaque. Vous aurez alors un code comme celui-ci dans l’en-tête, par exemple:

 typedef struct Point Point; Point * point_new(int x, int y); 

puis fournissez la déclaration de struct dans le fichier d’implémentation:

 struct Point { int x, y; }; Point * point_new(int x, int y) { Point *p; if((p = malloc(sizeof *p)) != NULL) { p->x = x; p->y = y; } return p; } 

Dans ce dernier cas, vous ne pouvez pas renvoyer le point par valeur, car sa déclaration est masquée pour les utilisateurs du fichier d’en-tête. C’est une technique largement utilisée par exemple dans GTK + .

UPDATE Notez qu’il existe également des projets C hautement considérés où cette utilisation de typedef pour masquer la struct est considérée comme une mauvaise idée, le kernel Linux étant probablement le projet le plus connu. Voir le chapitre 5 du document Linux Kernel CodingStyle pour les mots en colère de Linus. 🙂 Mon point est que le “devrait” dans la question n’est peut-être pas figé, après tout.

C’est incroyable combien de personnes se trompent. S’IL VOUS PLAÎT ne pas typedef structs dans C, il pollue inutilement l’espace de noms global qui est généralement très pollué déjà dans les grands programmes C.

En outre, les structures typedef’d sans nom de balise sont une cause majeure d’imposition inutile de relations d’ordre entre les fichiers d’en-tête.

Considérer:

 #ifndef FOO_H #define FOO_H 1 #define FOO_DEF (0xDEADBABE) struct bar; /* forward declaration, defined in bar.h*/ struct foo { struct bar *bar; }; #endif 

Avec une telle définition, n’utilisant pas les typedefs, il est possible pour une unité Compiland d’inclure foo.h pour obtenir la définition FOO_DEF . S’il ne tente pas de déréférencer le membre ‘bar’ de la structure foo il ne sera pas nécessaire d’inclure le fichier “bar.h”.

De plus, les espaces de noms étant différents entre les noms de balises et les noms de membres, il est possible d’écrire un code très lisible tel que:

 struct foo *foo; printf("foo->bar = %p", foo->bar); 

Comme les espaces de noms sont séparés, il n’y a pas de conflit dans le nommage des variables coïncidant avec leur nom de balise struct.

Si je dois maintenir votre code, je supprimerai vos structures typedef’d.

D’après un ancien article de Dan Saks ( http://www.ddj.com/cpp/184403396?pgno=3 ):


Les règles du langage C pour nommer les structures sont un peu excensortingques, mais elles sont plutôt inoffensives. Cependant, lorsqu’elles sont étendues à des classes en C ++, ces mêmes règles ouvrent de petites fissures à l’parsing des bogues.

En C, le nom apparaît dans

 struct s { ... }; 

est une balise Un nom de tag n’est pas un nom de type. Compte tenu de la définition ci-dessus, des déclarations telles que

 sx; /* error in C */ s *p; /* error in C */ 

sont des erreurs en C. Vous devez les écrire comme

 struct sx; /* OK */ struct s *p; /* OK */ 

Les noms des unions et des énumérations sont également des balises plutôt que des types.

En C, les balises sont distinctes de tous les autres noms (pour les fonctions, les types, les variables et les constantes d’énumération). Les compilateurs C conservent les balises dans une table de symboles qui est conceptuellement sinon séparée physiquement de la table contenant tous les autres noms. Ainsi, il est possible pour un programme C d’avoir à la fois une étiquette et un autre nom avec la même orthographe dans la même scope. Par exemple,

 struct ss; 

est une déclaration valide qui déclare la variable s de type struct s. Ce n’est peut-être pas une bonne pratique, mais les compilateurs C doivent l’accepter. Je n’ai jamais vu de raison pour laquelle C a été conçu de cette manière. J’ai toujours pensé que c’était une erreur, mais ça y est.

De nombreux programmeurs (y compris le vôtre) préfèrent penser aux noms de structure en tant que noms de type, ils définissent donc un alias pour la balise en utilisant un typedef. Par exemple, définir

 struct s { ... }; typedef struct s S; 

vous permet d’utiliser S à la place de struct s, comme dans

 S x; S *p; 

Un programme ne peut pas utiliser S comme nom de type et variable (ou constante de fonction ou d’énumération):

 SS; // error 

C’est bon.

Le nom de la balise dans une définition struct, union ou enum est facultatif. De nombreux programmeurs replient la définition de la structure dans le typedef et se dispensent de la balise, comme dans:

 typedef struct { ... } S; 

L’article lié a également une discussion sur la façon dont le comportement C ++ de ne pas exiger un typedef peut causer des problèmes de masquage de noms subtils. Pour éviter ces problèmes, il est également typedef de typedef vos classes et structures dans C ++, même si à première vue, cela semble inutile. En C ++, avec le typedef le masquage de nom devient une erreur dont le compilateur vous parle plutôt qu’une source cachée de problèmes potentiels.

L’utilisation d’un typedef évite d’avoir à écrire une struct chaque fois que vous déclarez une variable de ce type:

 struct elem { int i; char k; }; elem user; // comstack error! struct elem user; // this is correct 

Une autre bonne raison de toujours dactylographier les énumérations et les structures provient de ce problème:

 enum EnumDef { FIRST_ITEM, SECOND_ITEM }; struct StructDef { enum EnuumDef MyEnum; unsigned int MyVar; } MyStruct; 

Notez la faute de frappe dans EnumDef dans la structure (Enu u mDef)? Cela comstack sans erreur (ou avertissement) et est correct (selon l’interprétation littérale du standard C). Le problème est que je viens de créer une nouvelle définition d’énumération (vide) dans ma structure. Je ne suis pas (comme prévu) en utilisant la définition précédente EnumDef.

Avec un type similaire de typdef, des erreurs de compilation se seraient produites lors de l’utilisation d’un type inconnu:

 typedef { FIRST_ITEM, SECOND_ITEM } EnumDef; typedef struct { EnuumDef MyEnum; /* comstackr error (unknown type) */ unsigned int MyVar; } StructDef; StrructDef MyStruct; /* comstackr error (unknown type) */ 

Je préconiserais TOUJOURS des structures et des énumérations de typage.

Non seulement pour économiser de la frappe (sans jeu de mots;)), mais parce que c’est plus sûr.

Style de codage du kernel Linux Le chapitre 5 donne de bons avantages et inconvénients (principalement contre) de typedef .

S’il vous plaît ne pas utiliser des choses comme “vps_t”.

C’est une erreur d’utiliser typedef pour les structures et les pointeurs. Quand tu vois un

 vps_t a; 

dans la source, ça veut dire quoi?

En revanche, si on dit

 struct virtual_container *a; 

vous pouvez réellement dire ce qu’est un “a”.

Beaucoup de gens pensent que les typedefs “aident à la lisibilité”. Pas si. Ils ne sont utiles que pour:

(a) objects totalement opaques (où le typedef est activement utilisé pour masquer l’object).

Exemple: “pte_t” etc. Objets opaques auxquels vous ne pouvez accéder qu’en utilisant les fonctions d’accesseur appropriées.

REMARQUE! L’opacité et les “fonctions d’access” ne sont pas bonnes en elles-mêmes. La raison pour laquelle nous les avons pour des choses comme pte_t etc., c’est qu’il n’y a absolument aucune information accessible de manière portative.

(b) Effacer les types d’entiers, où l’abstraction permet d’ éviter toute confusion, que ce soit “int” ou “long”.

u8 / u16 / u32 sont des types parfaitement fins, bien qu’ils rentrent dans la catégorie (d) mieux qu’ici.

REMARQUE! Encore une fois, il doit y avoir une raison à cela. Si quelque chose est “unsigned long”, alors il n’y a aucune raison de le faire

 typedef unsigned long myflags_t; 

mais s’il y a une raison claire pour laquelle, dans certaines circonstances, il pourrait s’agir d’un “unsigned int” et d’autres configurations pourraient être “unsigned long”, alors allez-y et utilisez un typedef.

(c) lorsque vous utilisez sparse pour créer littéralement un nouveau type pour la vérification de type.

(d) Nouveaux types identiques aux types C99 standard, dans certaines circonstances exceptionnelles.

Bien que les yeux et le cerveau ne mettent que très peu de temps à s’habituer aux types standard tels que «uint32_t», certaines personnes s’opposent de toute façon à leur utilisation.

Par conséquent, les types «u8 / u16 / u32 / u64» spécifiques à Linux et leurs équivalents signés qui sont identiques aux types standard sont autorisés – bien qu’ils ne soient pas obligatoires dans le nouveau code de votre choix.

Lorsque vous modifiez du code existant qui utilise déjà l’un ou l’autre ensemble de types, vous devez vous conformer aux choix existants dans ce code.

(e) Types sûrs pour une utilisation dans l’espace utilisateur.

Dans certaines structures visibles pour l’espace utilisateur, nous ne pouvons pas exiger les types C99 et ne pouvons pas utiliser le formulaire ‘u32’ ci-dessus. Ainsi, nous utilisons des types __u32 et similaires dans toutes les structures partagées avec l’espace utilisateur.

Peut-être y a-t-il d’autres cas aussi, mais la règle devrait essentiellement être de ne JAMAIS utiliser un typedef à moins que vous ne puissiez clairement correspondre à l’une de ces règles.

En général, un pointeur, ou une structure comportant des éléments auxquels on peut accéder directement, ne devrait jamais être un typedef.

Je ne pense pas que les déclarations anticipées soient même possibles avec typedef. L’utilisation de struct, enum et union permet de transmettre des déclarations lorsque des dépendances (à propos de) sont bidirectionnelles.

Style: L’utilisation de typedef en C ++ est un peu logique. Cela peut presque être nécessaire lorsque vous manipulez des modèles nécessitant des parameters multiples et / ou variables. Le typedef aide à garder le nom droit.

Ce n’est pas le cas dans le langage de programmation C. L’utilisation de typedef ne sert le plus souvent à rien d’autre qu’à masquer l’utilisation de la structure de données. Étant donné que seul le nombre de frappes de touches {struct (6), enum (4), union (5)} est utilisé pour déclarer un type de données, il est pratiquement inutile d’utiliser l’aliasing de la structure. Est-ce que ces données sont une union ou une structure? L’utilisation de la simple déclaration non typée vous permet de savoir immédiatement de quel type il s’agit.

Remarquez comment Linux est écrit en évitant ssortingctement ce non-sens. Le résultat est un style minimaliste et propre.

Il se trouve qu’il y a des avantages et des inconvénients. Le livre «Expert C Programming» ( chapitre 3 ) est une source d’information utile. En bref, en C, vous avez plusieurs espaces de noms: balises, types, noms de membres et identificateurs . typedef introduit un alias pour un type et le localise dans l’espace de noms des balises. À savoir,

 typedef struct Tag{ ...members... }Type; 

définit deux choses. Une balise dans l’espace de noms de balise et un Type dans l’espace de noms de type. Vous pouvez donc faire à la fois Type myType et struct Tag myTagType . Les déclarations comme struct Type myType ou Tag myTagType sont illégales. En outre, dans une déclaration comme celle-ci:

 typedef Type *Type_ptr; 

nous définissons un pointeur sur notre type. Donc, si nous déclarons:

 Type_ptr var1, var2; struct Tag *myTagType1, myTagType2; 

alors var1 , var2 et myTagType1 sont des pointeurs vers le type mais myTagType2 pas.

Dans le livre mentionné ci-dessus, il est mentionné que les structures de type typedefing ne sont pas très utiles car elles ne font que sauver le programmeur de l’écriture du mot struct. Cependant, j’ai une objection, comme beaucoup d’autres programmeurs C. Bien qu’il arrive parfois que certains noms soient obscurcis (c’est pourquoi cela n’est pas conseillé dans les grandes bases de code comme le kernel) lorsque vous souhaitez implémenter le polymorphism dans C, cela aide beaucoup à chercher des détails . Exemple:

 typedef struct MyWriter_t{ MyPipe super; MyQueue relative; uint32_t flags; ... }MyWriter; 

tu peux faire:

 void my_writer_func(MyPipe *s) { MyWriter *self = (MyWriter *) s; uint32_t myFlags = self->flags; ... } 

Ainsi, vous pouvez accéder à un membre externe ( flags ) par la structure interne ( MyPipe ) via le casting. Pour moi, il est moins déroutant de lancer tout le type que de faire (struct MyWriter_ *) s; chaque fois que vous souhaitez effectuer une telle fonctionnalité. Dans ces cas, un bref référencement est un gros problème, surtout si vous employez fortement la technique dans votre code.

Enfin, le dernier aspect des types typedef ed est l’incapacité à les étendre, contrairement aux macros. Si, par exemple, vous avez:

 #define X char[10] or typedef char Y[10] 

vous pouvez alors déclarer

 unsigned X x; but not unsigned Y y; 

Cela ne nous concerne pas vraiment pour les structs car cela ne s’applique pas aux spécificateurs de stockage ( volatile et const ).

le nom que vous donnez (facultativement) à la structure s’appelle le nom de la balise et, comme cela a été noté, n’est pas un type en soi. Pour accéder au type, il faut le préfixe struct.

GTK + mis à part, je ne suis pas sûr que la variable soit utilisée comme un typedef pour le type de structure, donc en C ++ qui est reconnu et que vous pouvez omettre le mot-clé struct et aussi utiliser le nom de type:

struct MyStruct { int i; }; // The following is legal in C++: MyStruct obj; obj.i = 7;
struct MyStruct { int i; }; // The following is legal in C++: MyStruct obj; obj.i = 7; 

Commençons par les bases et progressons.

Voici un exemple de définition de structure:

 struct point { int x, y; }; 

Ici, le nom est facultatif.

Une structure peut être déclarée lors de sa définition ou après.

Déclaration lors de la définition

 struct point { int x, y; } first_point, second_point; 

Déclaration après définition

 struct point { int x, y; }; struct point first_point, second_point; 

Maintenant, notez soigneusement le dernier cas ci-dessus; Vous devez écrire un struct point pour déclarer des structures de ce type si vous décidez de créer ce type ultérieurement dans votre code.

Entrez typedef . Si vous envisagez de créer une nouvelle structure (la structure est un type de données personnalisé) ultérieurement dans votre programme en utilisant le même modèle, utiliser typedef lors de sa définition peut être une bonne idée, car vous pouvez économiser la saisie.

 typedef struct point { int x, y; } Points; Points first_point, second_point; 

Un mot de prudence en nommant votre type personnalisé

Rien ne vous empêche d’utiliser le suffixe _t à la fin de votre nom de type personnalisé, mais la norme POSIX réserve l’utilisation du suffixe _t pour indiquer les noms de type de bibliothèque standard.

typedef ne fournira pas un ensemble de structures de données co-dépendantes. Ce que vous ne pouvez pas faire avec typdef:

 struct bar; struct foo; struct foo { struct bar *b; }; struct bar { struct foo *f; }; 

Bien sûr, vous pouvez toujours append:

 typedef struct foo foo_t; typedef struct bar bar_t; 

Quel est exactement le but de cela?

A> un typdef facilite la signification et la documentation d’un programme en permettant la création de synonymes plus significatifs pour les types de données . De plus, ils aident à paramétrer un programme contre les problèmes de portabilité (K & R, pg147, lang prog).

B> une structure définit un type . Structs permet un regroupement pratique d’une collection de vars pour faciliter la manipulation (K & R, pg127, lang prog).

C> typedef’ing d’une structure est expliqué en A ci-dessus.

D> Pour moi, les structures sont des types personnalisés, des conteneurs, des collections, des espaces de noms ou des types complexes, tandis qu’un typdef n’est qu’un moyen de créer plus de pseudonymes.

En C99, typedef est requirejs. Il est obsolète, mais de nombreux outils (comme HackRank) utilisent c99 comme pure implémentation en C. Et typedef est nécessaire là-bas.

Je ne dis pas qu’ils devraient changer (peut-être avoir deux options C) si l’exigence changeait, ceux d’entre nous qui étudient pour des interviews sur le site seraient SOL.

Dans le langage de programmation ‘C’, le mot-clé ‘typedef’ est utilisé pour déclarer un nouveau nom à un object (struct, array, function..enum type). Par exemple, je vais utiliser un “struct-s”. Dans ‘C’, nous déclarons souvent une ‘structure’ en dehors de la fonction ‘principale’. Par exemple:

 struct complex{ int real_part, img_part }COMPLEX; main(){ struct KOMPLEKS number; // number type is now a struct type number.real_part = 3; number.img_part = -1; printf("Number: %d.%di \n",number.real_part, number.img_part); } 

Chaque fois que je décide d’utiliser un type de structure, j’aurai besoin de ce mot clé ‘struct’ quelque chose ” name ‘.’ Typedef ‘renommera simplement ce type et je pourrai utiliser ce nouveau nom dans mon programme à chaque fois. Donc notre code sera:

 typedef struct complex{int real_part, img_part; }COMPLEX; //now COMPLEX is the new name for this structure and if I want to use it without // a keyword like in the first example 'struct complex number'. main(){ COMPLEX number; // number is now the same type as in the first example number.real_part = 1; number.img)part = 5; printf("%d %d \n", number.real_part, number.img_part); } 

Si vous avez un object local (struct, array, Value) qui sera utilisé dans tout votre programme, vous pouvez simplement lui donner un nom en utilisant un ‘typedef’.

En tout cas, en langage C, struct / union / enum est une macro-instruction traitée par le préprocesseur en langage C (ne confondez pas avec le préprocesseur traitant “#include” et les autres)

alors :

 struct a { int i; }; struct b { struct a; int i; int j; }; 

struct b est dépensé comme quelque chose comme ceci:

 struct b { struct a { int i; }; int i; int j; } 

et ainsi, au moment de la compilation, il évolue sur stack de la manière suivante: b: int ai int i int j

cela aussi pourquoi il est difficile d’avoir des structures auto-référentes, C préprocesseur rond dans une boucle de déclaration qui ne peut pas se terminer.

typedef est un spécificateur de type, cela signifie que seul le compilateur C le traite et qu’il peut faire comme il le souhaite pour optimiser l’implémentation du code assembleur. Il ne faut pas non plus dépenser bêtement le membre de type comme le fait préprocessor avec les structures mais utiliser un algorithme de construction de référence plus complexe, de sorte que la construction comme:

 typedef struct a A; //anticipated declaration for member declaration typedef struct a //Implemented declaration { A* b; // member declaration }A; 

est autorisé et entièrement fonctionnel. Cette implémentation donne également access à la conversion de type compilateur et supprime certains effets de bug lorsque le thread d’exécution quitte le champ d’application des fonctions d’initialisation.

Cela signifie que dans C typedefs sont plus proches en tant que classe C ++ que les structures solitaires.