web-dev-qa-db-fra.com

Pourquoi devrions-nous dactylographier une structure si souvent en 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 nécessaire si souvent? Toute raison spécifique ou domaine applicable?

372
Manoj Doubts

Comme Greg Hewgill l’a dit, le typedef signifie que vous n’aurez plus à écrire struct partout. Cela permet non seulement d’économiser des frappes au clavier, mais aussi de rendre le code plus propre car il fournit un peu plus d’abstraction.

Choses comme

typedef struct {
  int x, y;
} Point;

Point point_new(int x, int y)
{
  Point a;
  a.x = x;
  a.y = y;
  return a;
}

devient plus propre quand vous n'avez pas besoin de voir le mot clé "struct" partout, il semble plus qu'il y ait vraiment un type appelé "Point" dans votre langue. Ce qui, après la typedef, est le cas, je suppose.

Notez également que, bien que votre exemple (et le mien) ait omis de nommer le struct lui-même, il est également utile de le nommer lorsque vous souhaitez fournir un type opaque. Ensuite, vous aurez un code comme celui-ci dans l'en-tête, par exemple:

typedef struct Point Point;

Point * point_new(int x, int y);

puis indiquez la définition 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éfinition est masquée pour les utilisateurs du fichier d'en-tête. C'est une technique largement utilisée dans GTK + , par exemple.

UPDATE Notez qu'il existe également des projets C très appréciés dans lesquels cette utilisation de typedef pour masquer struct est considérée Mauvaise idée, le noyau Linux est probablement le projet le plus connu. Voir le chapitre 5 de document Linux Kernel CodingStyle pour les mots en colère de Linus. :) Mon argument est que le "devrait" dans la question n'est peut-être pas figé dans la pierre, après tout.

431
unwind

C'est incroyable de voir combien de personnes se trompent. VEUILLEZ ne pas typer les structures en C, cela pollue inutilement l’espace de nom global qui est généralement déjà très pollué dans les grands programmes en C.

De plus, les structures typedef 'sans nom de balise sont une cause majeure d'imposition inutile de relations de commande 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, sans utilisation de 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, étant donné que les espaces de noms diffèrent 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);

Étant donné que les espaces de noms sont séparés, il n'y a pas de conflit en nommant des variables qui coïncident avec leur nom de balise struct.

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

197
Jerry Hicks

Extrait d'un ancien article de Dan Saks ( http://www.ddj.com/cpp/184403396?pgno= ):


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

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

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

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

struct s x;     /* OK */
struct s *p;    /* OK */

Les noms d'unions et d'é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 physiquement, séparée de la table contenant tous les autres noms. Ainsi, il est possible pour un programme C d'avoir à la fois une balise et un autre nom avec la même orthographe dans le même champ d'application. Par exemple,

struct s s;

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 pourquoi R était conçu de cette façon. J'ai toujours pensé que c'était une erreur, mais voilà.

De nombreux programmeurs (y compris le vôtre) préfèrent considérer les noms de structure comme des noms de types, de sorte qu'ils définissent un alias pour la balise à l'aide d'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 d’un type et d’une variable (ou d’une fonction ou d’une constante d’énumération):

S S;    // error

C'est bon.

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

typedef struct
    {
    ...
    } S;

L'article lié contient également une discussion sur la façon dont le comportement C++ consistant à ne pas requérir de typedef peut provoquer des problèmes de dissimulation de nom subtils. Pour éviter ces problèmes, il est judicieux de typedef vos classes et vos structures en C++ également, même si, à première vue, cela semble inutile. En C++, avec la variable typedef, le masquage de nom devient une erreur que le compilateur vous dit plutôt qu'une source masquée de problèmes potentiels.

137
Michael Burr

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

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct
59
Greg Hewgill

Une autre bonne raison de toujours typedef enums et structs résulte 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 mDef)? Ceci compile sans erreur (ni avertissement) et est correct (selon l'interprétation littérale de la norme 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 typdef, des erreurs de frappe similaires auraient entraîné des erreurs de compilation pour l'utilisation d'un type inconnu:

typedef 
{
  FIRST_ITEM,
  SECOND_ITEM
} EnumDef;

typedef struct
{
  EnuumDef MyEnum; /* compiler error (unknown type) */
  unsigned int MyVar;
} StructDef;
StrructDef MyStruct; /* compiler error (unknown type) */

Je recommanderais TOUJOURS la structure et les énumérations dactylographiées.

Non seulement pour sauver un peu de frappe (sans jeu de mots;)), mais parce que c'est plus sûr.

38
cschol

style de codage du noyau Linux Le chapitre 5 donne de grands avantages et inconvénients (principalement des inconvénients) de l'utilisation 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, qu'est-ce que cela signifie?

En revanche, si on dit

struct virtual_container *a;

vous pouvez réellement dire ce que "un" est.

Beaucoup de gens pensent que les types de caractères "aident à la lisibilité". Pas si. Ils ne sont utiles que pour:

(a) objets totalement opaques (où le typedef est activement utilisé pour masquer ce que l'objet est).

Exemple: "pte_t" etc. objets opaques auxquels vous ne pouvez accéder qu'en utilisant les fonctions d'accès appropriées.

REMARQUE! L'opacité et les "fonctions d'accès" ne sont pas bonnes en elles-mêmes. La raison pour laquelle nous les avons pour des choses comme pte_t etc. est qu'il y a vraiment zéro des informations accessibles de manière portative.

(b) Types d'entiers clairs, où l'abstraction aide à éviter toute confusion, qu'il s'agisse de "int" ou de "long".

u8/u16/u32 sont parfaitement typés, bien qu’ils rentrent mieux dans la catégorie (d).

REMARQUE! Encore une fois - il doit y avoir une raison pour 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 existe une raison claire pour expliquer pourquoi, dans certaines circonstances, il pourrait s'agir d'un "unsigned int" et, dans d'autres configurations, d'un "unsigned long", il est évident que vous devez utiliser un typedef.

(c) lorsque vous utilisez Sparse pour créer littéralement un nouveau type à des fins de vérification de type.

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

Bien que les yeux et le cerveau mettent peu de temps à s’habituer aux types standard tels que "uint32_t", certaines personnes s’objectent néanmoins à leur utilisation.

Par conséquent, les types 'u8/u16/u32/u64' spécifiques à Linux et leurs équivalents signés, identiques aux types standard, sont autorisés - bien qu'ils ne soient pas obligatoires dans votre nouveau code.

Lors de l'édition de code existant qui utilise déjà l'un ou l'autre des types de types, vous devez vous conformer aux choix existants dans ce code.

(e) Types sans danger pour une utilisation dans l'espace utilisateur.

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

Peut-être existe-t-il d'autres cas également, mais la règle devrait être de ne JAMAIS JAMAIS utiliser un typedef, à moins que vous ne puissiez clairement faire correspondre l'une de ces règles.

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

29
Yu Hao

Je ne pense pas que les déclarations en aval soient même possibles avec typedef. L'utilisation de struct, enum et union permet le transfert de déclarations lorsque les dépendances (à savoir) sont bidirectionnelles.

Style: L'utilisation de typedef en C++ est assez logique. Cela peut être presque nécessaire lorsqu'il s'agit de modèles nécessitant des paramètres 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 à obscurcir l'utilisation de la structure de données. Dans la mesure où seul {struct (6), enum (4), union (5)} nombre de frappes de touche sont utilisés pour déclarer un type de données, l'aliasing de la structure n'est pratiquement pas utilisé. Ce type de données est-il une union ou une structure? L'utilisation de la simple déclaration non typdefed vous permet de savoir immédiatement de quel type il s'agit.

Remarquez comment Linux est écrit en évitant strictement cet aliasing non-sens typedef. Le résultat est un style minimaliste et épuré.

11
natersoz

Il s'avère qu'il y a des avantages et des inconvénients. Le livre phare "Programmation Expert C" ( Chapitre ) 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 nom de la balise. À savoir,

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

définit deux choses. Une balise dans l'espace de noms de balises et une autre type dans l'espace de noms de types. Donc, vous pouvez faire à la fois Type myType et struct Tag myTagType. Les déclarations telles que struct Type myType ou Tag myTagType sont illégales. De plus, 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 sur Type mais myTagType2 pas.

Dans le livre mentionné ci-dessus, il est mentionné que les structures typedefing ne sont pas très utiles car elles permettent uniquement au programmeur d'écrire la structure Word. Cependant, j'ai une objection, comme beaucoup d'autres programmeurs C. Bien qu’il soit parfois obscurcissant certains noms (c’est pourquoi il n’est pas conseillé dans les bases de code volumineuses comme le noyau) d’implémenter le polymorphisme en C, cela aide beaucoup regardez ici pour plus de 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 transtypage. Pour moi, il est moins déroutant de transtyper le type entier que de faire (struct MyWriter_ *) s; à chaque fois que vous souhaitez exécuter une telle fonctionnalité. Dans ces cas, un bref référencement est un gros problème, surtout si vous utilisez abondamment la technique dans votre code.

Enfin, le dernier aspect des types typedefed est l’incapacité de 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;

Nous ne nous soucions pas vraiment de cela pour les structures car cela ne s'applique pas aux spécificateurs de stockage (volatile et const).

10
user1533288

Commençons par les bases et poursuivons notre chemin.

Voici un exemple de définition de structure:

struct point
  {
    int x, y;
  };

Ici, le nom point est optionnel.

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

Déclarer pendant la définition

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

Déclarant 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 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 avez l'intention 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 plan, utiliser typedef pendant sa définition pourrait être une bonne idée, car vous pouvez enregistrer certaines saisies à l'avenir. .

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

Points first_point, second_point;

Un mot de prudence lorsque vous nommez 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 désigner les noms de types de bibliothèques standard.

4
Asif

le nom que vous donnez (éventuellement) à la structure est appelé nom de balise et, comme il a été noté, n'est pas un type en soi. Pour obtenir le type nécessite le préfixe struct.

En dehors de GTK +, je ne suis pas sûr que le nom de la variable utilisé soit aussi commun que le typedef du type struct. Par conséquent, en C++, il est reconnu et vous pouvez omettre le mot-clé struct et utiliser également le nom de la variable:


    struct MyStruct
    {
      int i;
    };

    // The following is legal in C++:
    MyStruct obj;
    obj.i = 7;
3
philsquared

typedef ne fournira pas un ensemble co-dépendant de structures de données. Cela ne peut pas être fait avec typdef:

struct bar;
struct foo;

struct foo {
    struct bar *b;
};

struct bar {
    struct foo *f;
};

Bien sûr, vous pouvez toujours ajouter:

typedef struct foo foo_t;
typedef struct bar bar_t;

Quel est le but de cela?

1
natersoz

Un type aide à la signification et à la documentation d'un programme en permettant création de synonymes plus significatifs pour les types de données. En outre, ils aident à paramétrer un programme en fonction des problèmes de portabilité (K & R, p. 147, programme C).

B> ne structure définit un type. Structs permet de grouper facilement une collection de vars pour faciliter la manipulation (K & R, p. 127, prog. Lang.) En une seule unité

C> typer une structure est expliqué dans A ci-dessus.

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

1
JamesAD-0

Dans le langage de programmation 'C', le mot clé 'typedef' est utilisé pour déclarer un nouveau nom pour un objet (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.%d i \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 '' nom '.' Typedef 'renommera simplement ce type et je pourrai utiliser ce nouveau nom dans mon programme à tout moment. 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 objet local (struct, tableau, valeur) qui sera utilisé dans tout votre programme, vous pouvez simplement lui donner un nom en utilisant un 'typedef'.

0
RichardGeerify

Il s'avère que C99 typedef est requis. C’est obsolète, mais beaucoup d’outils (comme HackRank) utilisent c99 comme implémentation en C pur. 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 étudions pour des entretiens sur le site seraient SOL.

0