Utilisation de plusieurs fichiers

Pour qu'un ensemble de fonctions cohérentes puisse être réutilisé facilement, il convient de les regrouper dans un même fichier. Le fichier est en C à la base de ce qu'en programmation on nomme la modularité.

Cela permettra en outre de leur faire partager des variables sans que ces variables soient utilisées par des fonctions extérieures, ainsi que des fonctions auxilliaires. C'est ce qu'on appelle l'encapsulation.

Modularité et encapsulation sont deux des concepts les plus importants de la programmation, quelque soit le type de langage utilisé.

Exemple

Voici le fichier base2.c qui contient les calculs des puissances de 2 et de la partie entière supérieure du logarithme en base 2 d'un entier. base2.c contient également une fonction main destinée au test des fonctions présentes, ainsi qu'une fonction précisant l'usage de la commande ainsi construite.

La commande affiche les valeurs des fonctions puissance_de_2 et ilog pour des valeurs allant de 0 à l'entier passé en paramètre. Pour vérifier l'exactitude du résultat, on calcule également la partie entière supérieure du logarithme en base 2 à l'aide des fonctions de la bibliothèque mathématique log2 et ceil. La fonction ceil renvoyant un double, il est nécessaire de faire un cast (int) pour éviter un avertissement car la valeur affichée est un entier (%d).

Le fichier base2.c est compilé à l'aide de la commande
gcc -Wall -std=c99 base2.c -lm -o base2
L'option -l permet de lier une bibliothèque, ici la bibliothèque libm. Les bibliothèques ayant toutes le préfixe lib, celui-ci est omis dans l'option -l.

Voici un exemple d'exécution :
$ base2 10
0       1       0       -2147483648
1       2       0       0
2       4       1       1
3       8       2       2
4       16      2       2
5       32      3       3
6       64      3       3
7       128     3       3
8       256     3       3
9       512     4       4
10      1024    4       4

On souhaite séparer les fonctions réutilisables de base2 de la partie liée à leur test. Pour cela, on répartit les fonctions existantes dans deux fichiers : base2.c et test_base2.c. Il faut également créer un fichier d'entête base2.h contenant les déclarations des fonctions de base2.c.

On remarquera :
  1. les commentaires sur les fonctions de base2 sont dans le fichier d'entête. Ce fichier doit être lu par le "client" du module base2. Par contre, il n'a pas à regarder les fichiers .c contenant les implémentations.
  2. les directives du pré-processeur contenues dans base2.h ont pour objectif d'éviter les inclusions multiples qui entraînent des erreurs de compilation.
  3. Le fichier d'entête est inclus dans test_base2.c, mais aussi dans base2.c pour assurer la cohérence entre les déclarations et les définitions des fonctions.
  4. On a déclaré la fonction usage dans test_base2.c même si ce n'est pas indispensable.
La commande pour compiler l'ensemble est la suivante :
gcc -Wall -std=c99 base2.c test_base2.c -lm -o base2

On peut également compiler séparément chacun des fichiers sources et ensuite réaliser l'édition de lien. Cela permet lorsque beaucoup de fichiers sont utilisés de ne recompiler que ceux ayant été modifiés.
$ ls
base2.c         base2.h         test_base2.c
$ gcc -Wall -std=c99 -c base2.c
$ ls
base2.c         base2.h         base2.o         test_base2.c
$ gcc -Wall -std=c99 -c test_base2.c
$ ls
base2.c         base2.h         base2.o         test_base2.c    test_base2.o
$ gcc base2.o test_base2.o -lm -o base2
$ ls
base2           base2.h         test_base2.c
base2.c         base2.o         test_base2.o
$ base2 5
0       1       0       -2147483648
1       2       0       0
2       4       1       1
3       8       2       2
4       16      2       2
5       32      3       3

Afin d'automatiser ce processus, le système Unix propose l'outil make associé aux fichiers Makefile. Le fichier Makefile suivant, situé dans le même répertoire que les fichiers base2.h, base2.c, et test_base2.c permet ainsi d'obtenir le même résultat que précédemment en utilisant uniquement la commande make.
$ ls
Makefile        Makefile.bak    base2.c         base2.h         test_base2.c
$ make
gcc  -c -Wall -std=c99 base2.c
gcc  -c -Wall -std=c99 test_base2.c
gcc  -o base2 base2.o test_base2.o -lm
$ ls
Makefile        base2           base2.h         test_base2.c
Makefile.bak    base2.c         base2.o         test_base2.o
$ make clean
rm -f *.o core base2
$ ls
Makefile        Makefile.bak    base2.c         base2.h         test_base2.c

Utilisation de fonctions et variables statiques

Une variable qui est déclarée en dehors de toute fonction est dite globale.
Dans un fichier .c, on trouve deux sortes de fonctions et de variables globales: les externes et les statiques. Par défaut, ou si elle est déclarée explicitement externe à l'aide du mot clé extern, un fonction ou un variable est externe, c'est à dire qu'elle peut être utilisée dans tout le programme. Au contraire, un fonction ou une variable globale statique, (mot clé static), ne peut être utilisée qu'à l'intérieur du fichier ou elle est déclarée.

Par exemple, supposons que l'on veuille rajouter à base2.c une fonction qui affiche le mot binaire correspondant à un entier n. Cette fonction est en deux parties pour traiter le cas particulier du 0 : afficher_binaire(unsigned) et afficher_binaire_aux(unsigned). La deuxième fonction n'ayant d'utilité que dans le cadre de la première, elle sera déclarée static.
Les trois fichiers base2.c, base2.h et test_base2.c sont modifiés.

$ base2 5
0       1       0       -2147483648     0
1       2       0       0       1
2       4       1       1       10
3       8       2       2       11
4       16      2       2       100
5       32      3       3       101