Chapitre 1 Compilation C 1 e chapitre ne constitue pas un cours de compilation, naturellement, mais donne les bases nécessaires à l’utilisation raisonnée d’un compilateur. Après une présentation très générale d’un ordinateur, nous ferons la différence entre langage machine, assembleur et langages de haut niveau. Ensuite nous étudierons les différentes phases dans la compilation d’un logiciel. Modèle de l’ordinateur Le modèle d’ordinateur encore utilisé aujourd’hui correspond à l’architecture de Von Neumann. Ce dernier, mathématicien hongrois, l’a proposée en 1945. Le modèle est schématisé sur la figure 1.1. Unité de contrôle Unité arithmétique Registres Unité d’entrées sorties Mémoire contenant les programmes et les donnés Fig. 1.1 – Modèle de Von Neumann Le processeur de l’ordinateur, qui regroupe l’unité de contrôle, l’unité arithmétique et l’unité d’entrées/sorties est un condensé d’électronique, capable d’effectuer des actions qui ont été câblées. Ces actions, très primitives, constituent le jeu d’instructions de la machine. Programmer un ordinateur consiste à lui indiquer les instructions (les actions) qu’il doit effectuer, dans l’ordre. Un tel programme est dit programme en langage machine, et c’est le seul langage que l’ordinateur connaı̂t. Les actions possibles sont, par exemple, consulter le contenu de la mémoire ou des registres, y écrire, déclencher une opération d’entrées/sorties, ou bien modifier le déroulement du programme lui-même en omettant d’exécuter certains actions, ou en exécutant à nouveau certaines d’entre elles. Un processeur peut exécuter une seule instruction à la fois (en première approximation). Le texte du programme lui-même est écrit dans la mémoire. C’est le système d’exploitation (qui est lui même un programme, et qui est lancé au démarrage de la machine1 ) qui se charge d’exécuter tel ou tel programme inscrit en mémoire. 2 Langage machine Un programme en langage machine est difficilement lisible. Il est uniquement constitué de nombres. Certains de ces nombres codent les actions à effectuer, et d’autres, par exemple, des adresses en mémoire, ou des valeurs. On peut imaginer que chaque action, avec ses opérandes, est codée sur un nombre de 8 bits. Les deux premiers identifient l’opération, le suivant un numéro de registre et les cinq suivants une adresse en mémoire. L’opération de charger (Load) le contenu de l’adresse 12 dans le registre A (numéroté 0) sera codée par : 01 0 01100=76 si 1 Comme MacOS, GNU/Linux ou Windows. 1 2 Compléments Algo-Prog – Gea 2– l’action Load porte le code 01. Naturellement, les microprocesseurs actuels ont un jeu d’instructions plus large et des données plus grandes. En conclusion, un programme écrit en langage machine est clairement illisible. Une autre façon d’écrire un programme, plutôt que sous forme de nombres, constitue le langage assembleur2 . L’instruction précédente serait écrite Load A,12, ce qui est déjà plus évocateur que 76. Néanmoins, de nos jours, le langage assembleur est assez peu utilisé3 . Il a laissé sa place à essentiellement deux types de langages : les langages compilés et les langages interprétés. Dans le premier cas (C, C++, Fortran, Pascal, Cobol, Ada, ...), le programmeur écrit le code source de son programme dans un langage évolué, par exemple le C, qui n’est pas compréhensible par la machine. Puis, un logiciel nommé compilateur aura pour tâche de transformer ce code source en langage machine. Cette opération est appelée la compilation. Une fois transformé en langage machine, le programme est exécutable par la machine, mais n’est plus compréhensible par un humain. Une autre particularité des langages compilés est que les programmes exécutables sont dépendants de la plate-forme. Un programme en langage machine est en effet dépendant du type de microprocesseur (du type de matériel en général), et du système d’exploitation (à cause des appels-systèmes). Par conséquent, un programme compilé sur un PC Intel avec Linux ne fonctionnera pas sur le même PC Intel avec Windows, ni sur une machine à base de 68000 avec Linux... Un langage interprété (Basic, Logo, Lisp, Prolog...) s’utilise à l’aide d’un interpréteur. Un interpréteur est un programme qui va lire le code source, et exécuter une à une, chacune des opérations qu’il contient. Un programme écrit dans un langage interprété est souvent plus lent, en moyenne, qu’un programme compilé, pour la bonne raison que l’interpréteur doit, à la volée, comprendre et traduire le texte du programme pour demander à la machine de l’exécuter. 3 Compilation d’un programme en C Nous entendons ici par compilation les deux étapes suivantes : – la compilation proprement dite ; – puis d’édition de liens. La première étape transforme un fichier source C en fichier objet, (à peu de choses près des bouts de programme écrits en langage machine). L’étape d’édition de liens regroupe des fichiers objets entre eux de façon à obtenir un programme complet, écrit en langage machine, qui peut être exécuté. La distinction entre les deux étapes est souvent nécessaire à la compréhension des erreurs. Une erreur de compilation signifie généralement que le compilateur n’a pas compris ce qui est inscrit dans le code source. Une erreur d’édition de liens signifie souvent qu’il manque quelque chose aux fichiers objets pour créer un programme complet. La plupart des environnements de développement intégrés (IDE) maintiennent cette différence et la création d’un programme s’effectue en deux étapes : Compile, puis Link. Avec un compilateur en ligne de commandes comme gcc, ces deux étapes sont distinctes aussi : – gcc -c fic1.c Compilation de fic1.c, création de fic1.o – gcc -c fic2.c Compilation de fic2.c, création de fic2.o – gcc -o programme fic1.o fic2.o Édition de lien, les fichiers fic1.o et fic2.o sont regroupés pour former le programme nommé programme Ces deux étapes peuvent être regroupées en une ligne : – gcc -o programme fic1.c fic2.c mais il n’en reste pas moins vrai que les deux étapes sont effectuées successivement. L’intérêt de ce type de compilation (dite compilation séparée) est de minimiser les temps de compilation. Lorsqu’un fichier est modifié, seul celui-ci doit être recompilé, et l’édition de liens doit également être refaite. Lorsqu’une application est composée de plusieurs centaines de fichiers, alors qu’une compilation complète peut durer plus d’une heure, une recompilation due à une modification mineure ne prendra que deux ou trois minutes. Techniquement parlant, il est à noter qu’un fichier d’en-tête .h inclus dans un fichier .c fait partie intégrante de ce dernier. Donc la modification d’un .h implique la recompilation de tous les .c l’incluant (ainsi que l’édition de liens). Et de même si l’on modifie un .h inclus dans un autre .h lui-même inclus dans un .c... Les IDE se chargent de gérer et d’optimiser les compilations. En ligne de commandes, un outil spécial est chargé de ce travail : la commande make et le fichier Makefile. 2 Ce terme est utilisé par abus. L’assembleur est le logiciel qui transforme un programme écrit sous forme de mnémoniques en langage machine. Par extension, on appelle aussi assembleur le langage constitué par ces mnémoniques. 3 En ce qui concerne l’informatique domestique. En informatique industrielle ou embarquée, il n’est pas rare d’utiliser l’assembleur. 3 1. Compilation 4 4.1 Paramétrer Dev-C++ Chemins d’inclusions et de bibliothèques On suppose que les bibliothèques installées par le programmeur sont situées dans C :/usr/lib et que les fichiers d’en-tête correspondants sont dans C :/usr/include. Ces deux chemins doivent être précisés dans les boı̂tes suivantes Outils/Options du compilateur/Répertoires : Boı̂te 1 4.2 Boı̂te 2 Options d’édition de liens Les bibliothèques à lier doivent être précisées dans les options du projet (Projet / Options du projet / Paramètres), comme indiqué sur la boı̂te 3. 4.3 Option ((tous les warnings)) Vous devez prendre l’ahabitude de profiter de toutes les possibilités du compilateur pour vous aider. En particulier, certains Warnings sont passés sous silence par défaut. Une simple option indique au compilateur qu’il vous prévienne de constructions douteuses et vous permette de gagner beaucoup de temps de débuggage. L’option -Wall doit être indiquée à partir du menu (Projet / Options du projet / Paramètres) comme indiqué sur la boı̂te 4. Boı̂te 3 Boı̂te 4