C modules

Identification

Infoforall

Organisation programme C


ATTENTION : cette fiche ne constitue qu'une initiation à la structuration d'un projet en C. Ne la voyez pas comme une finalité mais comme une introduction. Certaines notions sont parfois présentées de façon simplifiées de façon à vous permettre de prendre assez facilement la main. Vous verrez plus tard comme faire cela de façon encore plus ordonnée et structurée.

Cette page vous explique comment organiser votre projet en répartissant vos codes entre plusieurs fichiers puis comment compiler l'ensemble.

Vous allez répartir votre code dans différents modules, chaque module étant défini à travers deux fichiers :

  1. Les fichiers d'en-tête (header) d'extension .h qui contiendront les informations publiques : ce qui doit être visible de l'exérieur pour parvenir à utiliser votre module :
    • Les prototypes et documentations des fonctions (publiques)
    • Les déclarations de structures (publiques)
    • Les typedef
    • Les constantes / macros
  2. Les fichiers sources d'extension .c qui contiendront la grande majorité de votre code dont vos fonctions privées : ces fichiers contiennent les codes effectifs de vos fonctions.
    • Le code des fonctions publiques et privées
    • Les fonctions privées (avec static)
    • Les détails d’implémentation
    • Les variables internes

1 - Exemple de programme avec module

Lorsque vous voulez créer du code qui soit réutilisable dans un autre cadre que votre projet, il ne faut pas le placer dans votre main.c mais dans deux autres fichiers d'extension .c et .h, disons personne.c.

Prenons un exemple simple avec un module contenant de quoi définir une Personne :
MONPROJET/ (répertoire / dossier) ├── personne.h ├── personne.c └── main.c

Cliquer pour obtenir le détail des différents fichiers.

Fichier personne.h (avec et sans documentation)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#ifndef PERSONNE_H // SI la macro n'est pas encore définie ALORS #define PERSONNE_H #include <stddef.h> // pour NULL #define NOM_MAX 100 // taille maximale du nom (modifiable) typedef struct { char nom[NOM_MAX]; // nom stocké directement dans la structure int age; } Personne; int initialiser_personne(Personne* p, const char* nom, int age); const char* get_name(const Personne* p); int get_age(const Personne* p); #endif // FIN du SI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
/* personne.h */ #ifndef PERSONNE_H // Si la macro n'est pas encore définie #define PERSONNE_H #include <stddef.h> // pour NULL #define NOM_MAX 100 // taille maximale du nom (modifiable) /** * Structure représentant une personne */ typedef struct { char nom[NOM_MAX]; // nom stocké directement dans la structure int age; } Personne; /** * @brief : Initialise une personne. * * @param p : pointeur vers la personne * @param nom : nom de la personne (copié dans le tableau) * @param age : âge de la personne * @return 0 si OK, -1 en cas d'erreur */ int initialiser_personne(Personne* p, const char* nom, int age); /** * @brief : Renvoie le nom de la personne. * * @param p : pointeur vers la personne * @return le nom (chaîne de caractères); NULL si erreur */ const char* get_name(const Personne* p); /** * @brief : Renvoie l’age de la personne. * * @param p : pointeur vers la personne * @return l’age, -1 si erreur */ int get_age(const Personne* p); #endif

Traduction en français des différentes lignes :

  • Ligne 2 : SI la macro PERSONNE_H n'a pas encore été définie alors réalise les lignes 3 à 38.
  • Cela permet d'utiliser include sans se soucier de savoir si son code a déjà été ramené dans le code source : si ce n'est pas le cas, on le ramène et on crée la macro pour s'en souvenir, sinon on ne fait rien puisqu'il n'y a rien sous le endif.

  • Ligne 38 : Fin du SI
  • Ligne 3 : Directive signifiant "crée une macro PERSONNE_H".
  • Lignes 10-13 : Création du type Personne qui est une STRUCTURE PUBLIQUE puisqu'on fournit sa structure exacte.
  • Lignes 15-.. : On fournit PROTOTYPES et DOCUMENTATIONS de fonctions.

Notez que j'intégre ici des versions d'accesseurs qui gèrent les éventuels envois de personnes mal configurés.

Autre technique possible : on peut choisir de ne rien tester MAIS de préciser comme PRECONDITIONS : personne valide. C'est alors à la personne qui appelle votre fonction de faire le test AVANT l'appel.

Fichier personne.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
#include <string.h> #include <stdio.h> #include "personne.h" int initialiser_personne(Personne* p, const char* nom, int age) { if (!p || !nom) return -1; strncpy(p->nom, nom, NOM_MAX - 1); p->nom[NOM_MAX - 1] = '\0'; p->age = age; return 0; } const char* get_name(const Personne* p) { if (!p) return NULL; return p->nom; } int get_age(const Personne* p) { if (!p) return -1; return p->age; }

Notez la différence de syntaxe pour la directive include entre les modules personnels et les modules natifs.

La directive demandant de recopier personne.h permet de récupérer la structure ET de vérifier que les déclarations anticipées (prototypes) correspondent bien aux prototypes des déclarations.

Fichier main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#include <stdio.h> #include "personne.h" int main(void) { Personne pers; if (initialiser_personne(&pers, "Alice", 25)) { printf("Erreur d'initialisation\n"); return 1; } printf("Nom : %s\n", get_name(&pers)); printf("Age : %d\n", get_age(&pers)); printf("Appuyez sur Entrée pour quitter..."); getchar(); return 0; }

Remarquez que le compilateur peut vérifier que la syntaxe utilisée dans main.c à l'aide du fichier .h.

Par contre, pour le moment, on ne sait pas réellement exécuter ces fonctions car elles sont dans deux fichiers sources différents. C'est l'éditeur de lien qui va faire le lien entre les différents fichiers .c.

La compilation est ensuite réalisée de cette façon :

1
gcc main.c personne.c -o programme
  • On lance un appel au programme gcc
  • en lui donnant en arguments d'appel les différents fichiers .c à lier,
  • l'option -o permet d'indiquer le nom qu'on veut donner à l'exécutable.

Il ne reste alors plus qu'à lancer votre exécutable (attention, sous Windows, le nom programme aura certainement été transformé en programme.exe) :

1 2 3 4
./programme Nom : Alice Age : 25 Appuyez sur Entrée pour quitter..

RAPPEL sur les commandes en ligne de code :

  • Trouver le répertoire actuel : .
  • Trouver le répertoire parent : ..
  • Se déplacer : cd chemin_a_suivre (ainsi cd ../.. veut dire de remonter deux fois dans l'arborescence.
  • Voir le contenu du répertoire courant : ls ou ls -al.

2 - Dans votre projet

Lors d'un projet, on doit classifier les fonctionnalités en grande thématique et on pourra alors créer un fichier .h et un fichier .c pour chacune de ces grandes familles. De façon naïve, on pourrait faire ceci (mais on verra comment faire mieux plus bas) :
MONPROJET (répertoire / dossier) | ├── FICHIERCSV (repertoire) | ├── fichiercsv.h | └── fichiercsv.c | ├── DONNEES (repertoire) | ├── donnees.h | └── donnees.c | ├── AFFICHAGE (repertoire) | ├── affichage.h | └── affichage.c | └── main.c

Vous pourrez inclure vos fichiers comme précédemment mais La compilation sera un peu différente car il faudra :

  1. Donner clairement la position des différences fichiers-sources
  2. Donner au compilateur la liste des dossiers à fouiller lorsqu'on utilise une directive #include.
1
gcc main.c FICHIERCSV/fichiercsv.c DONNEES/donnees.c AFFICHAGE/affichage.c -o programme -I FICHIERSCSV -I DONNEES -I AFFICHAGE
L'option -I veut dire d'Inclure le dossier PERSONNE dans les dossiers où chercher les fichiers demandés par les directives #include.

Comme vous le voyez, la commande de compilation peut rapidement devenir assez grande sur de vrais projets comportant beaucoup de modules. Il existe un programme nommé make qui permet de générer automatiquement cette compilation en allant lire un fichier de configuration nommé le Makefile.

Néanmoins, pour bien savoir utiliser ces configurations automatiques, il faut savoir... compiler à la main. Je ne vous donne donc ici aucune indication sur la création d'un Makefile. C'est volontaire. Commencez pour compiler manuellement. La flèche UP est votre amie !

En réalité, la plupart du temps, on organise un projet en plaçant les fichiers-source .c dans un répertoire nommé src et les fichiers d'en-tête dans un répertoire nommé include.

MONPROJET (répertoire / dossier) | ├── src | ├── fichiercsv.c | ├── donnees.c | └── affichage.c | ├── include | ├── donnees.h | ├── affichage.h | └─── fichiercsv.h | ├── build | ├── projetv1 | └── projetv2 | └── main.c

Avec cette structure, il suffira de dire d'inclure le répertoire include avec -I include. Le compilateur va donc chercher automatiquement les .h dans ce répertoire.

Pour indiquer les fichiers .c à l'éditeur de liens, on peut utiliser src/*.c

Enfin, pour indiquer de placer les exécutables dans le répertoire build, on utilisera -o version3.

1
gcc -I include main.c src/*.c -o build/version3

Nous n'imposerons aucune structure particulière ici. Savoir construire une belle structure de projet est un module en soi et vous aurez l'occasion d'avoir des modules de conception logicielle.

Quelques mini-remarques en pasant :

  • Vous pourriez partir sur une structure MVC : M pour Modèle (ce module gère les données de votre programme), V pour Vue (ce module gère l'interface offerte à l'utilisateur) et C pour Controleur (ce module fait la liaison entre le Modèle, la Vue et l'utilisateur). C'est le modèle usuel sur les sites Web dynamiques.
  • Vous pourriez partir sur une structure en Couche : chaque module ne communique qu'avec son module parent et son module enfant. C'est le modèle d'Internet.
  • Vous pourriez stocker vos fichiers-source (.c) dans un répertoire src et vos fichiers-en-tête (.h) dasn un répertoire include.

Aucune structure n'est imposée lors de ces premiers projets mais il faudra juste expliquer pourquoi vous avez structurer le votre de telle ou telle façon.

3 - Optionnel ? Avec malloc et free

Cette partie est purement optionnelle : elle n'est à utiliser que si vous êtes à l'aise avec la programmation en C.

Nous allons reprendre l'exemple précédent mais en cachant à l'utilisateur l'implémentation de nos personnes. Il ne pourra les créer qu'en passant par nos fonctions d'interface.

Il va donc falloir créer les structures permanentes en dehors du programme principal lui-même.

Pour cela, nous allons utiliser la fonction malloc() qui permet de demander l'attribution d'une zone-mémoire permaente (située dans le tas) et la fonction free() qui permet de libérer cette zone-mémoire une fois qu'on n'en a plus besoin.

Dans cette partie (avancée), nous utiliserons également la gestion mémoire pour stocker nos noms plutôt que de nous limiter à une taille fixe maximale (100 dans l'exemple de la partie 1).

La structure du code reste la même (si ce n'est que je vous montre comment placer les fichiers du module dans un répertoire :

MONPROJET (répertoire / dossier) ├── PERSONNE (repertoire) | ├── personne.h | └── personne.c | └── main.c

Cliquer pour obtenir le détail des différents fichiers.

Fichier personne.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
/* personne.h */ #ifndef PERSONNE_H // SI la macro n'est pas encore définie, ALORS #define PERSONNE_H #include <stddef.h> /** * Structure représentant une personne */ typedef struct PersonneInterne Personne; /** * Crée une nouvelle personne. * @param nom : nom de la personne (la fonction fait une copie) * @param age : âge de la personne * @return un pointeur vers la personne nouvellement créée, ou NULL si allocation échoue */ Personne* creer_personne(const char* nom, int age); /** * Libère la mémoire allouée pour une personne. * @param p : pointeur vers la personne */ void detruire_personne(Personne* p); /** * Renvoie le nom de la personne. * @param p : pointeur vers la personne * @return le nom (chaîne de caractères) */ const char* get_name(const Personne* p); /** * Renvoie l’âge de la personne. * @param p : pointeur vers la personne * @return l’âge */ int get_age(const Personne* p); #endif // SI la macro n'est pas encore définie, ALORS

Traduction en français des différentes lignes :

  • Ligne 10 : on définit un type Personne qui est un alias vers une structure qui n'est pas définie directement dans ce fichier : il s'agit donc d'une struture privée que l'utilisateur ne devra pas manipuler directement. Il doit se contenter d'utiliser les fonctions d'interface qu'on lui fournit.
  • Ligne 21 : on lui permet de créer une personne.
  • Ligne 27 : on lui permet de détruire une personne.
Fichier personne.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
#include <stdlib.h> #include <string.h> #include "personne.h" /* Définition réelle (privée) */ struct PersonneInterne { char* nom; int age; }; Personne* creer_personne(const char* nom, int age) { if (!nom) return NULL; Personne* p = malloc(sizeof(Personne)); if (!p) return NULL; p->age = age; p->nom = malloc(strlen(nom) + 1); if (!p->nom) { free(p); return NULL; } strcpy(p->nom, nom); return p; } void detruire_personne(Personne* p) { if (!p) return; free(p->nom); free(p); } const char* get_name(const Personne* p) { if (!p) return NULL; return p->nom; } int get_age(const Personne* p) { if (!p) return -1; return p->age; }

Notez qu'on y donne la défintion exacte de la structure interne privée.

On la manipule à travers le type Personne qui est un alias de struct PersonneInterne.

MONPROJET (répertoire / dossier) ├── PERSONNE (repertoire) | ├── personne.h | └── personne.c | └── main.c
Fichier main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include <stdio.h> #include "personne.h" int main() { Personne* alice = creer_personne("Alice", 25); if (!alice) { printf("Erreur : allocation mémoire\n"); return 1; } printf("Nom : %s\n", get_name(alice)); printf("Âge : %d\n", get_age(alice)); detruire_personne(alice); return 0; }

La compilation est ensuite réalisé de cette façon :

1
gcc main.c PERSONNE/personne.c -o programme -I PERSONNE
L'option -I veut dire d'Inclure le dossier PERSONNE dans les dossiers où chercher les fichiers demandés par les directives #include.

4 - FAQ

Qu'est-ce qui est imposé lors du projet alors ?

  • Votre projet devra comporter au moins trois modules dont un module contenant un jeu de tests : un ensemble d'appels et vérification de fonctions de façon à vérifier qu'elles répondent correctement. Cela permet de vérifier en temps réel qu'une modification faite pour rajouter quelque chose n'est casse pas une autre.
  • La documentation de vos fonctions.
  • Des assertions sur les fonctions pour la phase de conception. Lors de la réalisation de l'exécutable final, on désactivera les assert.

Article publié le 01 03 2026
Dernière modification : 03 03 2026
Auteur : ows. h.