Compilation

Portrait de marseillais

La compilation est une succession d'étapes qui transforment votre code en C en un fichier exécutable par la machine. Voici ces différentes étapes:

le Preprocessing

Le préprocesseur est un élément du compilateur qui va transformer le code en fonction de directives qui lui sont propres afin de préparer le code à être compilé.

En C, les directives du préprocesseur commencent toutes par #. Les plus connues sont #include et #define, mais dans la partie [[C Avancé]] on verra qu'il existe aussi #ifdef et #ifndef, #else, #endif, et bien d'autres.
C'est notament au niveau du préprocesseur que les fichiers .h sont inclus avec les fichiers C.

Une fois que le fichier a été préparer, le compilateur analyse le code pour passer à la génération du code assembleur correspondant.

La compilation

La compilation transforme le code C en assembleur propre au processeur: à partir de cet instant les fichiers compilés deviennent donc spécifiques à la machine.
Le code assembleur est déduit du code C et c'est à ce moment la qu'intervienne les optimisations éventuelles: déroulage de boucles, rotation de registre, etc.

La génération des fichiers objets - Assemblage

Une fois que la compilation est finie, il faut transformer ce code assembleur en code objet (code machine) utilisable par le microprocesseur. Le fichier devient alors complètement illisible. Il y a un fichier objet par fichier source en C. Ces fichiers portent l'extension .o.

Attention, c'est du code objet, mais pas du code exécutable! En effet, il ne doit y avoir qu'un seul exécutable, mais il peut y avoir plein de fichiers objets. La création de ce fichier "binaire" exécutable est dédiée au linker.

La liaison des objets pour la création du binaire

Lorsqu'un projet est divisé en plusieurs fichiers sources, il y a plusieurs fichiers objets: le but de cette étape est de lier les objets entre eux, ainsi que les bibliothèques (''librairies'' en anglais) statiques et dynamiques, afin de faire un fichier exécutable.

Le fichier créé par le linker est un fichier exécutable (binaire).

Intérêt de comprendre ces étapes

Mais pourquoi doit-on connaitre ces étapes? tout simplement parce que dans un projet dont le résultat doit être propre, il faut maitriser chacune des étapes: le bon programmeur:
* Ecrit des directives de preprocessing intelligente pour limiter le travail du compilateur
* Vérifie son code assembleur pour l'optimiser encore plus si besoin est (notamment en systèmes embarqués)
* Débuggue son programme en se servant d'un debugger qui agit sur les fichiers objets
* Fait de la programmation multifichier afin d'avoir des morceaux séparés, remplaçables, et si possibles liés dynamiquement afin de limiter la taille du binaire

Commentaire de Duke
Eventuellement ajouter le fait que l'on peut aussi créer des librairies.

Comprendre les erreurs de compilation

Comme la compilation se déroule en plusieurs phases, des erreurs peuvent intervenir dans chacune des phases. Pour les débugger correctement, il est important de savoir les identifier. En effet, ce n'est pas la peine de chercher dans les directives de preprocessing des erreurs de liens. Nous allons voir quelques erreurs classiques, apprendre à les identifier, et surtout à les corriger.

Les erreurs de préprocesseur

Les erreurs de préprocesseur arrivent vite: avant même que le code soit transformé en assembleur. En voici un exemple de programme qui va échouer au moment du preprocessing, avant meme la compilation (l'erreur du printf n'est même pas signalée):

 #include <stdio.h>
 #ifdef WIN32
 #include <conio.h>
 int main()
 {       
         printf(5);
 
         return 0;
 }

On voit que le #ifdef n'a pas été fermé par un #endif. Voici l'erreur correspondante:

f1.c:2:1: unterminated #ifdef

Les erreurs du préprocesseur sont donc très claires et faciles à corriger.

Les erreurs de code C

La première erreur est la plus fréquente:

f1.c: In function `main':
f1.c:6: error: syntax error before "return"

Cette erreur est provoquée à la compilation, par une erreur de syntaxe dans le code: un point virgule ou une parenthèse oubliée. Mais attention, l'erreur n'est pas à l'endroit indiqué, mais avant! Voici le code qui a provoquée l'erreur:

 int main()
 {
     printf("Hello World ! \n")
     return 0;
 }

On voit qu'il manque un ";" après le printf. On aurait l'erreur suivante si par exemple nous oubliions la ")" et pas le ";"

f1.c: In function `main':
f1.c:5: error: syntax error before ';' token

Une autre erreur classique est de se tromper de type lors d'un passage de paramètre: voici l'erreur correspondante:

f1.c: In function `main':
f1.c:5: warning: passing arg 1 of `printf' makes pointer from integer without a cast

provoquée par exemple par un
printf(4);

4 est un entier, et printf attend un pointeur. L'erreur inverse peut aussi se produire, lorsqu'on passe un pointeur à la place d'un entier. Il faut donc, dans une erreur de ce type, faire attention aux types signalés par le message d'erreur.

Pour la correction de cette erreur, il faut revérifier les prototypes des fonctions et les types des arguments passés.

Les erreurs de liens

Voila l'erreur de lien la plus courante:

/tmp/cconpIvx.o(.text+0x11): In function `main':
: undefined reference to `print_toto'
collect2: ld returned 1 exit status

Et le code correspondant:

 int main (){
         print_toto();
         return 0;
 }

La raison est que la fonction print_toto() est définie ailleurs (ou pas), mais déclarée nulle part dans le fichier de test. Au moment du lien, le linker ne trouve pas la définition du symbole, donc il n'est pas content.
En réalité, cette erreur est précédée d'un warning, souvent masqué (à moins d'utiliser l'option -Wall du compilateur), qui indique que la fonction est déclarée implicitement:

f1.c: In function `main':
f1.c:2: warning: implicit declaration of function `print_toto'

Voici l'autre erreur très courante que l'on rencontre au moment du link:

/tmp/ccr2MjbC.o(.text+0x0): In function `print_toto':
: multiple definition of `print_toto'
/tmp/ccYyznn1.o(.text+0x0): first defined here
/usr/lib/gcc-lib/i686-pc-linux-gnu/3.3.6/../../../../i686-pc-linux-gnu/bin/ld: Warning: size of symbol `print_toto' changed from 20 in /tmp/ccYyznn1.o to 25 in /tmp/ccr2MjbC.o
collect2: ld returned 1 exit status

Le problème ici est que la fonction print_toto() est définie à deux endroits différents: c'est uniquement au moment du link que le problème apparait car les 2 fichiers sont compilés séparément, et c'est au moment de la construction de la table des symboles générale, que le linker voit que le même symbole a été défini à deux endroits différents.

Le dénominateur commun de ces erreurs de link est qu'elles commencent toutes par:
/tmp/ccr2MjbC.o(.text+0x0):
ou quelque chose de similaire, et qu'elles finissent par:
collect2: ld returned 1 exit status

Comprendre les erreurs d'exécution

C'est l'heure de la légendaire...
Segmentation fault

La raison de cette erreur est que le programme tente d'accéder à une zone de mémoire non autorisée. Voila le code correspondant:

 char *s1, *s2;
 strcat(s1,s2);

Les deux chaines ont été déclarées, il n'y a donc aucune erreur à la compilation. En revanche, Ni l'une ni l'autre ne se sont vues allouées de mémoire. Dès lors, quand strcat() tente de les concatener, elle se heurte au fait que les pointeurs s1 et s2 sont NULL. C'est pourquoi au moment de l'exécution, la concaténation échoue.

D'une manière générale, les segmentation faults (segfaults pour les intimes) sont dues a une allocation mémoire oubliée: il faut donc revoir les pointeurs utilisés dans la zone du segfault, et vérifier que la mémoire vers laquelle ils pointent a été correctement initialisée.

La prochaine étape de ce cours (Organisation du Projet]) sera justement de voir comment améliorer son code et ses interventions dans ces différentes parties.

Commentaire de Duke :
Il pourrait être intéressant de rajouter les commandes gcc permettant de decouper la compilation :
par exemple gcc -E pour avoir le code apres le preprocesseur
Ceci juste comme une addition pour les "curieux" qui voudrait appronfondir d'eux meme
ce qu'il se passe exactement pendant la comilation

Sinon attention il y a des textes qui depassent dans les boites en pointillés
car elles ne retourne pas à la ligne toutes seules