Comment déclarer une structure dans un en-tête qui doit être utilisée par plusieurs fichiers en c?

115

Si j'ai un fichier source.c avec une structure:

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

Comment cette structure peut-elle être utilisée dans un autre fichier (ie func.c)?

Dois-je créer un nouveau fichier d'en-tête, y déclarer la structure et inclure cet en-tête func.c?

Ou devrais-je définir la structure entière dans un fichier d'en-tête et l'inclure dans les deux source.cet func.c? Comment la structure peut-elle être déclarée externdans les deux fichiers?

Dois-je le typedeffaire? Si c'est le cas, comment?

Ingénieur logiciel
la source
Notez que la définition de la structure n'est pas valide C.Au minimum, il devrait y avoir un point-virgule après l'accolade de fermeture pour struct b, mais votre structure adéclare un type qui n'est pas utilisé (vous devriez probablement définir un nom de membre, peut k- être , après l'accolade de fermeture interne et avant le point-virgule.
Jonathan Leffler

Réponses:

140

si cette structure doit être utilisée par un autre fichier func.c comment faire?

Lorsqu'un type est utilisé dans un fichier (ie fichier func.c), il doit être visible. Le pire moyen de le faire est de le copier-coller dans chaque fichier source dont il a besoin.

La bonne façon est de le mettre dans un fichier d'en-tête et d'inclure ce fichier d'en-tête chaque fois que nécessaire.

devons-nous ouvrir un nouveau fichier d'en-tête et y déclarer la structure et inclure cet en-tête dans func.c?

C'est la solution que j'aime le plus, car elle rend le code très modulaire. Je coderais votre structure comme:

#ifndef SOME_HEADER_GUARD_WITH_UNIQUE_NAME
#define SOME_HEADER_GUARD_WITH_UNIQUE_NAME

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

#endif

Je mettrais les fonctions utilisant cette structure dans le même en-tête (la fonction qui fait "sémantiquement" partie de son "interface").

Et généralement, je pourrais nommer le fichier après le nom de la structure, et utiliser à nouveau ce nom pour choisir les gardes d'en-tête définis.

Si vous devez déclarer une fonction à l'aide d'un pointeur vers la structure, vous n'aurez pas besoin de la définition complète de la structure. Une simple déclaration avant comme:

struct a ;

Sera suffisant, et cela diminue le couplage.

ou pouvons-nous définir la structure totale dans le fichier d'en-tête et l'inclure à la fois dans source.c et func.c?

C'est une autre façon, un peu plus simple, mais moins modulaire: certains codes n'ayant besoin que de votre structure pour fonctionner devraient quand même inclure tous les types.

En C ++, cela pourrait conduire à une complication intéressante, mais c'est hors sujet (pas de balise C ++), donc je ne vais pas élaborer.

puis comment déclarer cette structure comme extern dans les deux fichiers. ?

Je ne vois pas le point, peut-être, mais Greg Hewgill a une très bonne réponse dans son article Comment déclarer une structure dans un en-tête qui doit être utilisée par plusieurs fichiers en c? .

devons-nous le taper alors comment?

  • Si vous utilisez C ++, ne le faites pas.
  • Si vous utilisez C, vous devriez.

La raison en est que la gestion des structures C peut être pénible: vous devez déclarer le mot clé struct partout où il est utilisé:

struct MyStruct ; /* Forward declaration */

struct MyStruct
{
   /* etc. */
} ;

void doSomething(struct MyStruct * p) /* parameter */
{
   struct MyStruct a ; /* variable */
   /* etc */
}

Alors qu'un typedef vous permettra de l'écrire sans le mot clé struct.

struct MyStructTag ; /* Forward declaration */

typedef struct MyStructTag
{
   /* etc. */
} MyStruct ;

void doSomething(MyStruct * p) /* parameter */
{
   MyStruct a ; /* variable */
   /* etc */
}

Il est important que vous conserviez un nom pour la structure. L'écriture:

typedef struct
{
   /* etc. */
} MyStruct ;

créera simplement une structure anonyme avec un nom typé, et vous ne pourrez pas la déclarer en avant. Alors gardez le format suivant:

typedef struct MyStructTag
{
   /* etc. */
} MyStruct ;

Ainsi, vous pourrez utiliser MyStruct partout où vous voulez éviter d'ajouter le mot-clé struct, et toujours utiliser MyStructTag quand un typedef ne fonctionnera pas (ie déclaration forward)

Éditer:

Correction de l'hypothèse erronée concernant la déclaration de struct C99, comme l'a remarqué à juste titre Jonathan Leffler .

Modifier le 01/06/2018:

Craig Barnes nous rappelle dans son commentaire que vous n'avez pas besoin de garder des noms séparés pour le nom de la structure "tag" et son nom "typedef", comme je l'ai fait ci-dessus par souci de clarté.

En effet, le code ci-dessus pourrait bien s'écrire comme:

typedef struct MyStruct
{
   /* etc. */
} MyStruct ;

IIRC, c'est en fait ce que fait C ++ avec sa déclaration struct plus simple, dans les coulisses, pour le garder compatible avec C:

// C++ explicit declaration by the user
struct MyStruct
{
   /* etc. */
} ;
// C++ standard then implicitly adds the following line
typedef MyStruct MyStruct;

De retour en C, j'ai vu les deux usages (noms séparés et mêmes noms), et aucun n'a d'inconvénients que je connaisse, donc utiliser le même nom rend la lecture plus simple si vous n'utilisez pas des "espaces de noms" séparés en C pour les structures et autres symboles .

Paercebal
la source
2
Pouvez-vous commenter ou souligner la section de la norme C99 qui justifie votre commentaire «Si vous utilisez C99, ne le faites pas»?
Jonathan Leffler
Vous avez raison. J'ai récemment testé un C99 et j'ai été surpris de voir que mon struct untypedefed n'était pas reconnu lorsqu'il était utilisé à la manière C ++. J'ai cherché des options de compilateur, puis, dans tous les documents standard, je pouvais mettre la main sur, mais je n'ai rien trouvé qui puisse expliquer que je croyais que c'était possible ...
paercebal
2
... Alors de toute façon, merci pour la remarque. Je l'ai corrigé maintenant.
paercebal
Il n'est pas nécessaire d'utiliser des noms différents pour la structbalise et le typedefnom. C utilise un espace de noms différent pour les structbalises, vous pouvez donc l'utiliser MyStructpour les deux.
Craig Barnes
1
@CraigBarnes Vous avez raison, mais je voulais que ce soit clair simplement en lisant le code. Si j'avais donné le même nom, cela aurait pu confondre les débutants C sur la nécessité d'écrire le nom * deux fois "dans la même" déclaration ". J'ajouterai une note mentionnant votre commentaire. Merci!
paercebal
34

Pour une définition de structure qui doit être utilisée dans plus d'un fichier source, vous devez absolument la placer dans un fichier d'en-tête. Incluez ensuite ce fichier d'en-tête dans tout fichier source qui a besoin de la structure.

La externdéclaration n'est pas utilisée pour les définitions de structure, mais est plutôt utilisée pour les déclarations de variables (c'est-à-dire, une valeur de données avec un type de structure que vous avez défini). Si vous souhaitez utiliser la même variable dans plus d'un fichier source, déclarez-la comme externdans un fichier d'en-tête comme:

extern struct a myAValue;

Ensuite, dans un fichier source, définissez la variable réelle:

struct a myAValue;

Si vous oubliez de le faire ou le définissez accidentellement dans deux fichiers source, l'éditeur de liens vous en informera.

Greg Hewgill
la source
Vous pourriez obtenir une erreur de l'éditeur de liens, ou pas. En C, le modèle de liaison permet des définitions «communes», donc plusieurs définitions sans initialiseur (et peut-être avec le même initialiseur) peuvent fonctionner. C'est une «extension commune». Certains compilateurs prennent également en charge les définitions provisoires (manquantes), je crois.
Jonathan Leffler
Ensuite, dans un fichier source, définissez la variable réelle:; ... ou définissez-le accidentellement dans deux fichiers source ... :)
Johannes Schaub - litb
Une technique que j'ai utilisée pour le problème de déclaration / définition consiste à définir conditionnellement GLOBALcomme externou rien en haut de l'en-tête, puis à déclarer les variables comme GLOBAL struct a myAValue;. À partir de la plupart des fichiers source, vous organisez la #define GLOBAL externversion à utiliser (en déclarant les variables) et à partir d'exactement un fichier source, vous utilisez la définition vide pour que les variables soient définies .
TripeHound
Vous pouvez avoir le même nom de structure que le nom typedef en C, mais pas en C ++.
xuhdev
6

ah:

#ifndef A_H
#define A_H

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

#endif

là vous allez, il vous suffit maintenant d'inclure ah dans les fichiers où vous souhaitez utiliser cette structure.

fmsf
la source