Compilation séparée
Reprenons le code vu précédemment et séparons le code du fichier degres.c
en unités fonctionnelles :
- un fichier
main.c
qui regroupera uniquement le programme principal - un fichier
celcius.c
qui regroupera uniquement les fonctions de conversions
Ce que l'on sait de la compilation nous permet de comprendre commet organiser ces deux fichiers :
- le fichier
main.c
peut utiliser des fonctions non déclarées si :- il connaît leurs signatures
- elles sont inclues à l'édition de lien
- le fichier
celcius.c
peut être inclut uniquement à l'édition de lien et peut être compilé de façon séparée
Fichiers
Fichier main.c
:
#include <stdio.h>
double fahrenheit(int celcius);
int kelvin(int celcius);
int main() {
printf("%f \n", fahrenheit(37));
printf("%i \n", kelvin(37));
}
Fichier celcius.c
:
double fahrenheit(int celcius) {
return (celcius * 9.0/5) + 32;
}
int kelvin(int celcius) {
return celcius + 273;
}
On peut ensuite procéder à notre compilation en plusieurs étapes :
- compilation des objets :
clang -c main.c
clang -c celcius.c
- édition de lien :
clang main.o celcius.o
Si l'on ne modifie qu'un seul fichier, disons le fichier main.c
il n'est pas nécessaire de recompiler celcius.c
Il nous reste cependant une dépendance entre les deux fichiers : la déclaration des fonctions de celcius.c
dans main.c
.
Fichiers d'entêtes
Pour ne pas avoir à déclarer explicitement les fonctions de celcius.c
dans main.c
, on va utiliser un fichier annexe qui va les contenir :
Fichier celcius.h
à inclure au projet dans le même dossier que main.c
et celcius.c
:
double fahrenheit(int celcius);
int kelvin(int celcius);
Le fichier main.c
devient alors :
#include <stdio.h>
#include "celcius.h"
int main() {
printf("%f \n", fahrenheit(37));
printf("%i \n", kelvin(37));
}
Notez que comme le fichier celcius.h
est spécifique au projet (il est dans le même dossier que le reste du programme), on utilise des "
pour l'inclure. Le préprocesseur C
allant chercher ces fichiers en priorité dans le projet.
Prévenir l'inclusion multiple
A chaque fois qu'un fichier aura besoin d'utiliser des fonction de celcius.c
, il lui faudra inclure son fichier de headers celcius.h
. S'il est inclus plusieurs fois, le préprocesseur le recopiera plusieurs fois.
Par exemple le fichier test.c
:
#include "celcius.h"
#include "celcius.h"
int main() {
return 0;
}
Donnera après la phase du préprocesseur (clang -E test.c
) le fichier :
# 1 "celcius.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 418 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "celcius.c" 2
# 1 "./celcius.h" 1
double fahrenheit(int celcius);
int kelvin(int celcius);
# 2 "celcius.c" 2
# 1 "./celcius.h" 1
double fahrenheit(int celcius);
int kelvin(int celcius);
# 3 "celcius.c" 2
int main() {
return 0;
}
Qui inclut bien deux fois dans le même fichier le fichier celcius.h
.
POur éviter ceci on ajoute une astuce qui utilise le préprocesseur. Le fichier celcius.h
sera écrit :
#ifndef FILE_CELCIUS
#define FILE_CELCIUS
double fahrenheit(int celcius);
int kelvin(int celcius);
#endif
A la première lecture, la constante FILE_CELCIUS
n'existe pas et on écrit tout le fichier, à la deuxième la constante est définie et donc le préprocesseur saute la partie définition.
La preuve en regardant le résultat de la phase du préprocesseur du fichier test.c
avec ce nouveau fichier d'entête :
# 1 "celcius.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 418 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "celcius.c" 2
# 1 "./celcius.h" 1
double fahrenheit(int celcius);
int kelvin(int celcius);
# 2 "celcius.c" 2
int main() {
return 0;
}
Résumé
Séparez votre programme en autant de d'unités sémantiques. Chaque unité est composée :
- d'un fichier d'entête (
.h
) contenant les signatures des différentes fonctions mises à disposition du programme - d'un fichier
.c
contenant le corps de ces fonctions
Lorsque l'on veut utiliser une fonction définie dans une unité sémantique, on inclut son .h
en utilisant les "
(à la différence des inclusions système qui sont inclues entre <>
).
Enfin, la fonction main
qui constitue le programme principal est ans son fichier à part, nommé main.c
.
La compilation se fait en deux temps :
- compilation en objet
.o
en utilisant l'option-c
- édition de liens de différents fichiers objets
Dans le cadre de notre exemple, on a 3 fichiers :
celcius.h
:
double fahrenheit(int celcius);
int kelvin(int celcius);
#ifndef FILE_CELCIUS
#define FILE_CELCIUS
double fahrenheit(int celcius);
int kelvin(int celcius);
#endif
celcius.c
:
double fahrenheit(int celcius) {
return (celcius * 9.0/5) + 32;
}
int kelvin(int celcius) {
return celcius + 273;
}
main.c
:
#include <stdio.h>
#include "celcius.h"
int main() {
printf("%3.2f \n", fahrenheit(37));
printf("%i \n", kelvin(37));
}
Phases de compilation :
- compilation en objet :
clang -c main.c celcius.c
qui produit les fichiersmain.o
etcelcius.c
- édition de lien :
clang main.o celcius.o
qui produit le fichiersa.out