Question

J'ai l'habitude de faire tout mon codage dans un seul fichier C.Cependant, je travaille sur un projet suffisamment important pour qu'il devienne impossible de le faire.Je les ai #inclus ensemble, mais j'ai rencontré des cas où j'inclus certains fichiers plusieurs fois, etc.J'ai entendu parler des fichiers .h, mais je ne suis pas sûr de leur fonction (ni pourquoi avoir 2 fichiers vaut mieux que 1).

Quelles stratégies dois-je utiliser pour organiser mon code ?Est-il possible de séparer les fonctions « publiques » des fonctions « privées » pour un fichier particulier ?

Ce Cette question a précipité mon enquête.Le fichier tea.h ne fait aucune référence au fichier tea.c.Le compilateur « sait-il » que chaque fichier .h a un fichier .c correspondant ?

Était-ce utile?

La solution

Vous devez considérer les fichiers .h comme fichiers d'interface de votre fichier .c.Chaque fichier .c représente un module avec un certain nombre de fonctionnalités.Si les fonctions d'un fichier .c sont utilisées par d'autres modules (c'est-à-direautres fichiers .c) placez le prototype de fonction dans le fichier d'interface .h.En incluant le fichier d'interface dans le fichier .c de vos modules d'origine et dans tous les autres fichiers .c dans lesquels vous avez besoin de la fonction, vous rendez cette fonction disponible pour d'autres modules.

Si vous n'avez besoin d'une fonction que dans un certain fichier .c (pas dans un autre module), déclarez sa portée statique.Cela signifie qu'il ne peut être appelé qu'à partir du fichier c dans lequel il est défini.

Il en va de même pour les variables utilisées dans plusieurs modules.Ils doivent aller dans le fichier d'en-tête et y être marqués avec le mot-clé « extern ».Note:Pour les fonctions, le mot-clé 'extern' est facultatif.Les fonctions sont toujours considérées comme « externes ».

Les protections d'inclusion dans les fichiers d'en-tête permettent de ne pas inclure plusieurs fois le même fichier d'en-tête.

Par exemple:

Module1.c :

    #include "Module1.h"

    static void MyLocalFunction(void);
    static unsigned int MyLocalVariable;    
    unsigned int MyExternVariable;

    void MyExternFunction(void)
    {
        MyLocalVariable = 1u;       

        /* Do something */

        MyLocalFunction();
    }

    static void MyLocalFunction(void)
    {
      /* Do something */

      MyExternVariable = 2u;
    }

Module1.h :

    #ifndef __MODULE1.H
    #define __MODULE1.H

    extern unsigned int MyExternVariable;

    void MyExternFunction(void);      

    #endif

Module2.c

    #include "Module.1.h"

    static void MyLocalFunction(void);

    static void MyLocalFunction(void)
    {
      MyExternVariable = 1u;
      MyExternFunction();
    }

Autres conseils

Essayez de faire en sorte que chaque .c se concentre sur un domaine de fonctionnalité particulier.Utilisez le fichier .h correspondant pour déclarer ces fonctions.

Chaque fichier .h doit avoir une protection « en-tête » autour de son contenu.Par exemple:

#ifndef ACCOUNTS_H
#define ACCOUNTS_H
....
#endif

De cette façon, vous pouvez inclure "accounts.h" autant de fois que vous le souhaitez, et la première fois qu'il sera vu dans une unité de compilation particulière sera la seule à extraire réellement son contenu.

Compilateur

Vous pouvez voir un exemple de « module » C sur ce sujet - Notez qu'il existe deux fichiers : l'en-tête tea.h et le code tea.c.Vous déclarez toutes les définitions publiques, variables et prototypes de fonctions auxquels vous souhaitez que d'autres programmes accèdent dans l'en-tête.Dans votre projet principal, vous allez #include et ce code peut désormais accéder aux fonctions et variables du module tea mentionnées dans l'en-tête.

Après, cela devient un peu plus complexe.Si vous utilisez Visual Studio et de nombreux autres IDE qui gèrent votre build pour vous, ignorez cette partie : ils se chargent de compiler et de lier les objets.

Éditeur de liens

Lorsque vous compilez deux fichiers C distincts, le compilateur produit des fichiers objets individuels - donc main.c devient main.o et tea.c devient tea.o.Le travail de l'éditeur de liens consiste à examiner tous les fichiers objets (votre main.o et tea.o) et à faire correspondre les références. Ainsi, lorsque vous appelez une fonction tea dans main, l'éditeur de liens modifie cet appel pour qu'il appelle réellement le bon. fonction dans le thé.L'éditeur de liens produit le fichier exécutable.

Il y a un super tuto qui approfondit ce sujet, y compris la portée et les autres problèmes que vous rencontrerez.

Bonne chance!

-Adam

Quelques règles simples pour commencer :

  1. Placez les déclarations que vous souhaitez rendre « publiques » dans le fichier d'en-tête du fichier d'implémentation C que vous créez.
  2. Seuls les fichiers d'en-tête #include dans le fichier C sont nécessaires à l'implémentation du fichier C.
  3. inclure les fichiers d'en-tête dans un fichier d'en-tête uniquement si cela est requis pour les déclarations dans ce fichier d'en-tête.

  4. Utilisez la méthode include guard décrite par Andrew OU utilisez #pragma une fois si le compilateur le prend en charge (ce qui fait la même chose – parfois plus efficacement)

Pour répondre à votre question supplémentaire :

CeCette question a précipité mon enquête.Le fichier Tea.h ne fait aucune référence au fichier thé.c.Le compilateur "sait-il" que chaque fichier .h a un fichier .c correspondant?

Le compilateur ne s'intéresse pas principalement aux fichiers d'en-tête.Chaque appel du compilateur compile un fichier source (.c) en un fichier objet (.o).Dans les coulisses (c.-à-d.dans le make ou fichier projet), une ligne de commande équivalente à celle-ci est générée :

compiler --options tea.c

Le fichier source #includeIl s'agit de tous les fichiers d'en-tête des ressources auxquelles il fait référence, c'est ainsi que le compilateur trouve les fichiers d'en-tête.

(Je passe sous silence certains détails ici.Il y a beaucoup à apprendre sur la construction de projets C.)

En plus des réponses fournies ci-dessus, un petit avantage de diviser votre code en modules (fichiers séparés) est que si vous devez avoir des variables globales, vous pouvez limiter leur portée à un seul module en utilisant le mot clé ' statique'.(Vous pouvez également appliquer cela aux fonctions).Notez que cette utilisation de « static » est différente de son utilisation dans une fonction.

Votre question montre clairement que vous n’avez pas vraiment fait beaucoup de développement sérieux.Le cas habituel est que votre code sera généralement beaucoup trop volumineux pour tenir dans un seul fichier.Une bonne règle est que vous devez diviser la fonctionnalité en unités logiques (fichiers .c) et que chaque fichier ne doit pas contenir plus que ce que vous pouvez facilement garder en tête à la fois.

Un produit logiciel donné inclut alors généralement la sortie de nombreux fichiers .c différents.La façon dont cela se fait normalement est que le compilateur produit un certain nombre de fichiers objets (dans les fichiers ".o" des systèmes Unix, VC génère des fichiers .obj).C'est le but de "l'éditeur de liens" de composer ces fichiers objets dans la sortie (soit une bibliothèque partagée, soit un exécutable).

Généralement, vos fichiers d'implémentation (.c) contiennent du code exécutable réel, tandis que les fichiers d'en-tête (.h) contiennent les déclarations des fonctions publiques dans ces fichiers d'implémentation.Vous pouvez très facilement avoir plus de fichiers d'en-tête qu'il n'y a de fichiers d'implémentation, et parfois les fichiers d'en-tête peuvent également contenir du code en ligne.

Il est généralement assez inhabituel que les fichiers d'implémentation s'incluent les uns les autres.Une bonne pratique consiste à s'assurer que chaque fichier d'implémentation sépare ses préoccupations des autres fichiers.

Je vous recommande de télécharger et de consulter la source du noyau Linux.Il est assez massif pour un programme C, mais bien organisé en domaines de fonctionnalités distincts.

Les fichiers .h doivent être utilisés pour définir les prototypes de vos fonctions.Ceci est nécessaire pour que vous puissiez inclure les prototypes dont vous avez besoin dans votre fichier C sans déclarer toutes les fonctions dont vous avez besoin dans un seul fichier.

Par exemple, lorsque vous #include <stdio.h>, cela fournit les prototypes pour printf et d'autres fonctions IO.Les symboles de ces fonctions sont normalement chargés par le compilateur par défaut.Vous pouvez consulter les fichiers .h du système sous /usr/include si vous êtes intéressé par les idiomes normaux impliqués dans ces fichiers.

Si vous écrivez uniquement des applications triviales avec peu de fonctions, il n'est pas vraiment nécessaire de tout modulariser en regroupements logiques de procédures.Cependant, si vous avez besoin de développer un grand système, vous devrez alors réfléchir à l'endroit où définir chacune de vos fonctions.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top