Compilation
En gros, la compilation est le processus qui prend un ou plusieurs fichiers sources et produit un exécutable. En fait, comme on le verra, c'est une synecdoque particularisante (figure de style où on parle du tout en utilisant un terme pour une partie seulement). Pour éviter cette figure de style malheureuse, on peut parler de génération d'un exécutable (build en anglais).
Préprocessing
En C, les mots-clés commençant par un #
correspondent à un traitement de préprocessing. Par exemple #define X 32
remplace les occurrences de X
par 32
.
#define X 32
int main() {
int a = X + 2;
int b = X;
}
devient
int main() {
int a = 32 + 2;
int b = 32;
}int x
La ligne #include "bloup.h"
insère le contenu du fichier bloup.h
:
#include "bloup.h"
int main() {
bloup_create();
bloup_inform(3);
}
devient
void bloup_create();
void bloup_inform(int x);
void bloup_extract(int x, int y);
void bloup_free();
int main() {
bloup_create();
bloup_inform(3);
}
Compiler le projet en un coup
Voici une commande pour construire un exécutable :
gcc myotherCfile.c main.c -o main -Wall
où le flag -o
veut dire output.
- Quel défaut y-a-t-il de compiler tout ?
Compiler un projet fichier par fichier
En fait, on peut compiler chaque fichier source séparemment puis tout lier à la fin :
gcc -c -o myotherCfile.o myotherCfile.c
gcc -c -o main.o main.c
gcc -o main main.o myotherCfile.o
où le flag -c
signifie que l'on ne fait compiler mais pas lier.
Comme le montre l'image ci-dessous :
- la compilation consiste à transformer un fichier source .c en fichier objet (du code machine) .o mais en laissant des "trous" pour les fonctions qui sont définis dans d'autres modules.
- la liaison vient remplir les trous avec le code machine manquant.
Chaîne de compilation
C Source Code
|
v
+----------------+
| Preprocessor |
+----------------+
|
v
+----------------+
| Compiler |
+----------------+
|
v
+----------------+
| Assembler |
+----------------+
|
v
+----------------+
| Linker |
+----------------+
|
v
Executable
Makefile
Pourquoi a-t-on besoin d'un outil pour compiler automatiquement ?
Compiler avec Makefile ( version naïve)
- Créer un fichier
Makefile
. - Y écrire :
all:
gcc myotherCfile.c main.c -o main -Wall
Dans le terminal, on tape make
pour construire le projet. Le nom all
s'appelle une cible.
Attention, la ligne d'après contient un tab (et pas 4 espaces !) suivi de la commande à exécuter pour construire le projet.
Compiler intelligente avec Makefile
Avec un Makefile, on peut avoir plusieurs cible.
all: main
main: main.o myotherCfile.o
gcc -o main main.o myotherCfile.o
main.o: main.c
gcc -c -o main.o main.c
myotherCfile.o: myotherCfile.c
gcc -c -o myotherCfile.o myotherCfile.c
clean:
rm *.o main
Compilation et liaison
La cible main
a besoin d'avoir déjà effectué le travail pour les cibles main.o
et myotherCfile.o
, et consiste à effectuer gcc -o main main.o myotherCfile.o
.
-
Où est-ce qu'a lieu la liaison dans le Makefile ci-dessus ?
Dans la cible main. -
Qu'est ce que fait
gcc -c -o main.o main.c
?
main.c
en main.o
en laissant des trous pour les fonctions déclarées mais non définies.
Variables dans un MakeFile
On peut définir des constantes dans un MakeFile. Par exemple, on définit la constante CC
qui donne le nom du compilateur.
Pour avoir le contenu de la constante on écrit $(CC)
. Ecrire CC
ça écrit juste CC
; nous on veut le contenu.
CC=gcc
all: main
main: main.o myotherCfile.o
$(CC) -o main main.o myotherCfile.o
main.o: main.c
$(CC) -c -o main.o main.c
myotherCfile.o: myotherCfile.c
$(CC) -c -o myotherCfile.o myotherCfile.c
clean:
rm *.o main
Pattern
Voici trois règles qui ont le même pattern :
myotherCfile.o: myotherCfile.c
$(CC) -c -o myotherCfile.o myotherCfile.c
bloup.o: bloup.c
$(CC) -c -o bloup.o bloup.c
miaou.o: miaou.c
$(CC) -c -o miaou.o miaou.c
Au lieu de cela, on peut écrire :
%.o: %.c
$(CC) -c -o $@ $^
- Le
%
signifienimportequelnomdefichier
. $@
= le nom de la règlenimportequelnomdefichier.o
$^
= la prémisse, icinimportequelnomdefichier.c
nom de la règle: | prémisse |
---|---|
$@ | $^ |
Lister les fichiers
La commande principale pourrait être :
main: main.o myotherCfile.o bloup.o miaou.o
$(CC) -o main main.o myotherCfile.o bloup.o miaou.o
Pour réaliser cela, on a besoin de lister les .o
. Or, on ne les connait pas encore. Mais on sait qu'il y a en un par fichier source .c
. On peut lister les fichiers sources avec la commande wildcard
:
SOURCES=$(wildcard *.c)
La fonction wildcard
prend un argument qui est une expression régulière de fichiers et elle produit la liste des fichiers qui correspondent à l'expression régulière. Dans l'exemple, la constante SOURCES
vaut main.c myotherCfile.c bloup.c miaou.c
.
Pour obtenir la liste des .o
correspondantes, on fait une substitution :
main.c myotherCfile.c bloup.c miaou.c
🡇
main.o myotherCfile.o bloup.o miaou.o
Pour cela on écrit :
OBJECTS=$(SOURCES:.c=.o)
Et maintenant, la règle principale qui était :
main: main.o myotherCfile.o bloup.o miaou.o
$(CC) -o main main.o myotherCfile.o bloup.o miaou.o
devient
main: $(OBJECTS)
$(CC) -o main $^
Aller plus loin
On peut faire des boucles et autres en Makefile
... bref...