Annexe Compilation separee
Télécharger le Annexe Compilation separee en pdf
Pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
Page 1 : Informatique IICompilation séparée
Page 2 : Problématiques• Code du TD tris de tableaux : 309 lignes de code !• Code trop long : compliqué de rechercher les différentes parties qui nous intéressent / que l’on souhaite modifier• Code long = compilation lente tout le code va être inspecté même les parties qui ne sont pas utilisées• Gestion des fonctions : les différentes fonctions doivent êtreécrites dans le bon ordre si elles s’appellent entre elles
Page 3 : Problématique : appel croiséS’il existe un appel croisé de fonctions, chacune doit être écrite avant l'autre pour que le compilateur puisse les utiliser : ce n'est pas possible implicitement.void funcA……funcB…;// déclaration implicite !…void funcB……funcA…;// appel croisé…
Page 4 : Solution : déclaration explicite// déclaration explicite des prototypes de fonctions !void funcA…;void funcB…;void funcA……funcB…;// pas d'erreur à la compilation…void funcB……funcA…;// pas d'erreur à la compilation…
Page 5 : Problématique : modules• Il est possible d’écrire un code en plusieurs parties ou modules• Chaque module peut être compilé séparément. Cependant séparer le code en modules doit s’accompagner d’un moyen pour chacun des modules d’interagir avec les autres sans conflit• Si l’on souhaite fournir un module de code compilé sans les sources, il faut pouvoir au moins fournir les prototypes des fonctions à appeler, les constantes utilisées, ou les types redéfinis comme les structures par exemple
Page 6 : Problématique : modules• Il existe 2 grands types de fichiers lorsque l’on écrit un code séparé en modules en langage C1.Les fichiers contenant des instructions classiques, du code. Dans notre cas des fichiers .c exemple : main.c, tris.c, …1.Les fichiers d’entête, ou headers. Ce sont les fichiers .h. ils permettent de rassembler les “entêtes” communs à plusieurs fichiers sources et/ou autres fichiers d’entête.
Page 7 : Problématique : modules•Le fichier d'entête :•est généralement nommé comme le fichier .c qu'il accompagne•contient les inclusions d'autres fichiers d'entête nécessaires•contient les déclarations de fonctions prototypes•contient les définitions de constantes define …•contient les variables globales utilisées•contient les redéfinitions de types typedef notamment celles pour les structures•ne contient pas la déclaration de la fonction main !
Page 8 : Problématique : modulesvoid triSelectionint tab;void triRapide int tab,int debut,int fin;int partition…;tris.hincludestdio.hdefine TAILLE 1000main.cint mainint tabTAILLE;triSelectiontab;…return 0;functionstri.cvoid triRapide……pivot=partition…;…int partition………void triSelection……Problème : comment communiquentles fichiers entre eux ?
Page 9 : Les directives de préprocesseur• Certaines commandes ne sont pas exécutées lors de l’exécutionmais lors de la première phase de compilation. Ce sont les directives de préprocesseurs.• Les directives de préprocesseurs sont précédées de • Elles effectuent des modifications textuelles sur le fichier source:• Exemple :• define permet de remplacer une expression par une autre définition de constante ou de macro• include permet d'incorporer dans le fichier source le texte figurant dans un autre fichier. includestdio.h permet d’inclure le code de la bibliothèque stdio au code
Page 10 : Les directives de préprocesseur•Il est possible d’obtenir le fichier intermédiaire après la phase de préprocessing avec l’option –E dans gcc.•Par défaut cette commande ne créé pas de fichier, mais renvoie le résultat sur la sortie standard. Il est possible de stocker cette sortie dans un fichier toujours avec l‘option -omain.c// constantedefine TAILLE 15// macrodefine COUCOU printf"Coucou"int mainint tabTAILLEforint i=0; iTAILLE; i++…COUCOU;return 0 ;main.iint mainint tab15forint i=0; i15; i++… printf"Coucou";return 0 ;gcc -E main.c -o main.i
Page 11 : Problématique : modules• Différents modules doivent pouvoir s’échanger des informations : il faut notamment que les fichiers .c puisse avoir accès aux information des fichiers .h• La directive de préprocesseur include : permet d'inclure un fichier dans un autre. Cela permet de fusionner le contenu de plusieurs fichiers de manière dynamique et de fournir le résultat à la phase de compilation.include "fichierHeader.h"• Il faut donc inclure, entre autres, les fichiers .h utiles dans les fichiers.c
Page 12 : Problématique : modules•Exemplemain.cinclude "tris.h"int mainint tabTAILLE;triSelectiontab;…return 0;void triSelectionint tab;void triRapideint tab,int debut, int fin;int partition…;void triRapide……pivot=partition…;…int partition………void triSelection……tris.hincludestdio.hdefine TAILLE 1000functionstri.cinclude "tris.h"
Page 13 : include "functions.h"int mainfuncA…;return 0;Solution : fichier d'entêtevoid funcA…;void funcB…;include "functions.h"void funcA……funcB…;…void funcB……funcA…;…functions.hfunctions.cmain.c
Page 14 : Solution : fichier d'entête• Il faut donc pouvoir être capable de compiler chaque fichier C indépendemment des autres. L'option –c de gcc permet cette opération :• gcc –c functions.c -o functions.o• gcc –c main.c -o main.o• Les fichiers issus de cette compilation sont des fichiers objets.o c’est à dire un fichier intermédiaire dans le processus de compilation• Il faut maintenant réunir/fusionner tous les "objets" morceaux de codes compilés en un seul exécutable : toujours grâce à gcc mais cette fois-ci, les fichiers d'entrée ne sont plus les fichiers .c mais les fichiers .o générés précédemment :• gcc functions.o main.o -o myExec
Page 15 : Solution : fichier d'entêtefunctions.hfunctions.cmain.cfunctions.omain.ogcc –c …gcc –c …gcc …
Page 16 : Problématique : inclusions circulaires•Maintenant que nous pouvons compiler indépendamment les différents fichiers C de notre application, il apparait un nouveau problème•Il est possible que deux modules s'incluent respectivement : création d'une boucle d'inclusion infinie•Ces inclusions mutuelles ne sont pas forcément directes•le Module A est inclus par le module B•le module B est inclus par le module C•le module C est inclus par le module A•Il va donc falloir se prémunir des multiples inclusions en utilisant les instructions de compilation conditionnelle•Ces instructions permettent de "désactiver" des portions de code en fonction de paramètres statiques configuration du programme
Page 17 : Exemple 1 : inclusions circulairesmoduleA.hinclude "moduleC.h"void funcA…;moduleB.hinclude "moduleA.h"void funcB…;moduleC.hinclude "moduleB.h"void funcC…;include "moduleA.h"int mainfuncA…;return 0;main.c
Page 18 : Solution : compilation conditionnelle• Idée: introduire un « ordre » d’inclusion grâce à des conditions.• Rappel : define : permet de définir une macro, qui est une chaine de caractères servant à remplacer, soit des constantes, soit des suites d'instructions• Les directives préprocesseurs ifdef / ifndef … elif … else … endif : ces instructions conditionnelles testent l'existence de macros définies ou non, et incluent ou non la portion de code C associée.define CONST 15ifdef CONST// Instructions// compilées…endifdefine CONST 15ifndef CONST// Instructions// NON compilées…endif
Page 19 : Solution : compilation conditionnelleifndef MODULEAHdefine MODULEAHinclude "moduleB.h"…endifmoduleA.hifndef MODULEBHdefine MODULEBHinclude "moduleA.h"…endifmoduleB.h•Le fait de définir une constante MODULEAH nom totalement arbitraire dans l'absolu, mais dans la pratique on reprend le nom du fichier la 1ère fois que le fichier moduleA.h est inclus, va permettre, lors des prochaines inclusions, de "masquer" l'ensemble du contenu du fichier•Cette méthode permet d'inclure les différentes dépendances dans un ordre quelconque et simplifie grandement le développement
Page 20 : ifndef MODULECHdefine MODULECHinclude "moduleB.h"endifvoid funcA…;Exemple 1 : inclusions circulairesinclude "moduleA.h"int mainfuncA…;return 0;main.cmoduleA.hifndef MODULEAHdefine MODULEAHinclude "moduleC.h"endifvoid funcA…;moduleB.hifndef MODULEBHdefine MODULEBHinclude "moduleA.h"endifvoid funcA…;moduleC.h
Page 21 : moduleA.hvoid funcA…;include "functions.h"include "functions.h"int mainfuncA…;return 0;main.c...void funcA…;void funcA…;......int mainfuncA…;return 0;main.i•gcc –E main.c -o main.iERREURExemple 2 : inclusions multiplesexemple simplifié
Page 22 : functions.hinclude "functions.h"include "functions.h"int mainfuncA…;return 0;ifndef FUNCTIONSHdefine FUNCTIONSHvoid funcA…;endifmain.c…void funcA…;.........int mainfuncA…;return 0;main.i•gcc –E main.c -o main.iOKExemple 2 : inclusions multiplesexemple simplifié
Page 23 : include "parameters.h"if OS == WINDOWS...elif OS == LINUX...elif OS == MACOS...elseerror Bad value for 'OS'endifsystem.hExemple 3 : code conditionnelparameters.hdefine WINDOWS 1define LINUX 2define MACOS 3define OS LINUXinclude "system.h"int main… return 0;main.c
Page 24 : •Maintenant nous allons voir les différentes phases de l’exécutable gcc et les options associées :PREMIERE ETAPE :gcc –E main.c -o main.iL'option –E de gcc pré-traitement permet de voir en sortie le programme C qui sera effectivement compilé•Le code non compilé entre les instructions ifndef / ifdef ne va plus apparaître•Toutes les macros ont été remplacées par leurs valeurs•L’ensemble des fichiers inclus par le fichier C est fusionné dans l’ordre d’inclusion : il n’y a donc plus qu’un seul fichier à compilerGCC : phases de compilation
Page 25 : •Maintenant nous allons voir les différentes phases de l’exécutable gcc et les options associées :DEUXIEME ETAPE :gcc –S main.i -o main.sL'option –S de gcc compilation, permet de transformer le code C précédent en code assembleur toujours lisible par l'humain… •C’est cette phase qui va donc interpréter la syntaxe du code C et qui écrit régulièrement sur la console que vous avez oublié un point-virgule, qu’un symbole n’est pas déclaré ou qu’il y a un souci dans l’organisation des accolades•Un compilateur est spécifique à une cible physique ou une famille de cibles : le code assembleur généré est donc spécifique à un composant physique particulierGCC : phases de compilation
Page 26 : •Maintenant nous allons voir les différentes phases de l’exécutable gcc et les options associées :TROISIEME ETAPE :gcc –c main.s -o main.oL'option –c de gcc compilation permet de transformer le code assembleur en code machine, c’est à dire en un code que seul le composant cible peut interpréter•Il est donc difficile ici de pouvoir lire le code généré d’une manière fluide comme on pourrait le faire en C•C’est donc une partie de notre exécutable qui se trouve dans ce fichier, avec certains symboles qui n’ont pas encore été résolus telles que les fonctions appelées qui sont situées dans d’autres modules d’autres fichiers C qui ne sont pas forcément encore compilésGCC : phases de compilation
Page 27 : •Maintenant nous allons voir les différentes phases de l’exécutable gcc et les options associées :QUATRIEME ETAPE :gcc main.o -o main.exeL’exécutable gcc sans option édition de liens permet de fusionner les différents codes machines fichiers objets dans un seul exécutable que notre machine pourra alors exécuter•Ici gcc cherche à résoudre tous les symboles pour relier les morceaux de code entre eux. Il va “coller” bout à bout l’ensemble des fonctions et va déterminer l’adresse de code pour chacune d’entre elles : ensuite lors d’un appel de fonction, le code effectue un “saut” de code à la bonne adresse•C’est à cette étape que gcc écrit sur le terminal qu’il n’a pas trouvé telle ou telle fonction quand on utilise la lib math et que l’on ne met pas l’option -lm par exempleGCC : phases de compilation
Page 28 : •include : permet d'inclure un fichier dans un autre. Cela permet de fusionner le contenu de plusieurs fichiers de manière dynamique et de fournir le résultat à la phase de compilation•define : permet de définir une macro terme consacré, qui est une chaine de caractères servant à remplacer, soit des constantes, soit des suites d'instructions verbeuses et nombreuses. Le but est de fournir le moyen d'écrire un code plus concis et lisible•undef : permet "d'oublier" une macro définie précédemment•ifdef / ifndef … elif … else … endif : ces instructions conditionnelles testent l'existence de macros définies ou non, et permet d’inclure ou non la portion de code C associée. Nous avons vu leur utilisation lors de la résolution de la problématique d'inclusions multiples, mais ces directives servent aussi à fournir différentes versions de code en fonction de paramètres utilisateursQuelques directives du pré-processeur
Page 29 : •if defined… : effectue la même action que la directive ifdef … à la différence que la seconde ne peut tester que la définition d’une macro, alors que la première peut effectuer plusieurs comparaisons, combinées ensemble grâce à des opérateurs logiques :if definedLINUX defined ANDROID•if !defined… : effectue la même action que la directive ifndef …avec la même spécificité qu’énoncée au pragraphe précédent•if … == … : permet d'effectuer des comparaisons entre des valeurs de macros et/ou des valeurs entières, pour construire conditionnellement un programme. Fonctionne aussi avec les opérateurs == , != , , , = , =•error : stoppe la compilation et affiche le message qui suit la directive. Permet d'indiquer une configuration du programmeQuelques directives du pré-processeur
Page 30 : •Macros prédéfinies : il existe des macros qui permettent de récupérer toutes sortes d'informations sur votre programme :FILE : chemin du fichier courant chaîneLINE : numéro de ligne du fichier courant entierDATE : date de compilation chaîne "Feb 23 2022"TIME : heure de compilation chaîne "23:59:59"•Ces macros peuvent être utilisées pour afficher des détails sur le programme notamment en cas d’erreur par exemple•Pour les utiliser comme paramètres de fonctions comme printf, il faudra bien entendu indiquer le type adéquat d ou s•Bien évidemment ces valeurs sont des constantes pour le fichier actuellement compilé et d’un fichier C à un autre, les valeurs de DATE et TIME peuvent changer par exempleQuelques directives du pré-processeur
Page 31 : Informatique IIMake et makefile
Page 32 : Projet : schéma des dépendancestri.hmain.cfonctiontri.cfonctiontableau.cfonctiontableau.ofonctiontri.omain.oexec
Page 33 : Problématiques : nombre de commandes• Code modulé = plus pratique et lisible mais nécessite plus de commandes de compilation: chaque fichier .c doit être compiléséparémentgcc –c fonctiontri.c -o fonctiontri.ogcc –c fonctionstableau.c –o fonctionstableau.o…gcc –c main.c -o main.ogccfonctiontri.o fonctiontableau.o … main.o -o myExec• il faut aussi gérer l'arbre des dépendances et l'ordre des commandes gcc à exécuter !•Pénibilité, risque d’erreur, impossible de demander à un utilisateur de faire cela.Perte de temps : il est inutile de tout recompiler : seuls les fichiers modifiés doivent l’être.• Il faut automatiser la compilation.
Page 34 : Solution : make et makefile• make est un logiciel utilisé par les système UNIX qui permet de construire automatiquement des executables à partir de fichierssources.• make utilise des fichiers de configurations appelés souvent Makefile•le Makefile:• décrit les dépendances entre les fichiers• Se base sur les dates des fichiers pour savoir ce qui est à jour•peut compiler des fichiers, les déplacer, créer des dossiers, construire des bibliothèques, lancer des tests, créer des fichiers d'archives, effectuer des commits sur un dépôt git, lancer la génération de documentation ex: avec Doxygen• utilise des commande shell langage de script des systèmes UNIX
Page 35 : Solution : make et makefile• make est un logiciel utilisé par les systèmes UNIX qui permet de construire automatiquement des executables à partir de fichierssources.• make utilise des fichiers de configurations appelés souvent Makefile•Exemple de Makefile :
Page 36 : Structure d'un Makefile• Le makefile permet entre autresde générer les fichiers issus de la compilation.• Un Makefile est composé de règles pour la génération ou l'exécution de commandes.• Structure d'une règle :Cible fichier à générerDépendances fichiers nécessaires pour générer la cible Commandes à exécuter si les dépendances existent.
Page 37 : Makefile : structure Ligne de commentaire est une tabulation pas d'espace !cible1 : cible2 cible3cible2 : dependanceA dependanceBcommande shellcommande shellcible3 : dependanceA dependanceCcommande shellCible4 :commande shellAux tabulations !
Page 38 : •Pour lancer un makefile :make•Si l'on souhaite exécuter une cible particulière :make cible•Pour forcer un fichier spécifique :make –f filepath cible•Les commandes ne sont exécutées que si les dépendances existent et sont plus récentes que la cible ! = entre 2 appels à make, seuls les fichiers modifiés et ceux dont ils dépendent seront recompilés.Makefile : utilisation
Page 39 : •Si la cible est omise make la première cible du fichier sera exécutée.•On nomme souvent la première cible all : elle ne contient que des cibles dépendantes et n'a pas de commande.Makefile : cibles particulières
Page 40 : •Il existe des règles sans dépendance comme clean qui serviront à exécuter sans condition des commandes de nettoyage de l'environnement par exempleMakefile : cibles particulières
Page 41 : •Le Makefile précédent n'est pas dynamique : il dépend du nom des fichiers et de leurs nombres.•On peut complexifier le makefile pour le rendre plus adaptable aux fichiers sources.•Il est possible de :•Utiliser des variables•Lister les différents fichiers•Utiliser différentes commandes shell...Makefile : makefile dynamique
Page 42 : •Variables prédéfinies:• @ représente la cible• ^ représente la liste des dépendances• représente la 1ère dépendance• Plus difficile à lire mais plus pratique à utiliser.• Modifier le makefile précédentMakefile : Les variables prédéfinies
Page 43 : Makefile : Les variables prédéfinies
Page 44 : Makefile : Les variables •On peut définir ses propres variables. Ex:•nomVariable=valeur•CC = gcc•MYFILE=functions.c functions.h•DIR=projet/build•Pour utiliser les variables :nomVariable•Modifier le makefile précédent
Page 45 : Makefile : Les variables
Page 46 : Makefile : wildcard•Le code précédent est toujours dépendant des noms de fichiers.•L’instruction wildcard permet l’utilisation de caractères jokers pour remplacer n’importe quelle chaîne:SRC = wildcard .cDans notre exemple :SRC = main.c fonctiontri.c fonctiontableau.c•Il est également possible de parcourir de manière récursive tous les sous-répertoires:SRC = wildcard /.c
Page 47 : Makefile : les substitutions•En pratique on n'utilise pas directement la liste des .c mais la liste des .o.•Il est possible de modifier le nom des suffixes d'une liste, c'est la substitution :OBJ = SRC:.c=.oDans notre exemple :OBJ = main.o fonctiontri.o fonctiontableau.o•Modifier le makefile précédentAux espaces !
Page 48 : Makefile : les substitutions
Page 49 : Makefile : variables prédéfinies•Variables prédéfinis:• @ représente la cible• ^ représente la liste des dépendances• représente la 1ere dépendance• permet de définir un pattern• …• On peut ainsi raccourcir grandement le makefile!• Modifier le makefile précédent
Page 50 : Makefile : variables prédéfinies
Page 51 : Makefile : les fichiers•On peut créer les .o et l'exécutable dans un dossier séparé souvent appelé build.•Il faudra alors toujours indiquer le chemin des cibles :•Exemple:build/.o : .c tri.h BUILDDIRCC -c -o •Si le dossier n'existe pas, il faut le créer et utiliser la cible comme dépendance pour les autres cibles qui doivent être stockées dedans:build/:mkdir -p build/
Page 52 : Makefile : les fichiers
Page 53 : Makefile : les phony• Chaque cible définie est vue par make comme étant un fichier physique sur le disque.• Il est possible de rajouter des étapes à la compilation, de nouvelles cibles, dont les noms ne font pas forcément référence à des fichiers : ce sont les cibles PHONY• Une cible phony sera toujours exécutée, comme les cibles sans dépendances, mais à l’inverse de ces dernières, on peut ajouter des dépendances aux cibles phony
Page 54 : Makefile : les phony...VERSION = 1.2 script pour envoyer l’archive dans un dépôt de code, ....PHONY commitcommit : archivegit commit -m ….. script pour envoyer un mail après création executable, ....PHONY mailmail : execbash envoyermail.sh…
Page 55 : Makefile : une multitude d’options• Il existe plusieurs instructions pour gérer les listes de fichiers :•V1 = notdir V0 enlève les noms de dossiers•V2 = dir V0 enlève les noms de fichiers•V3 = suffix V0 extrait seulement les extensions•V4 = basename V0 enlève les extensions•V5 = addprefix dir/, V0ajoute un prefixe à chaque fichier•V6 = addsuffix .c, V0 ajoute un suffixe à chaque fichier• Les différentes possibilités de make sont très nombreuses• La documentation officielle se trouve sur le site de gnu.orghttps://www.gnu.org/software/make/manual/htmlnode/
Page 56 : Résumé et conclusionA partir de maintenant :• On écrit les prototypes des fonctions dans des fichier .h• On module son code.• au minimum:• Un fichier pour la fonction main• Un fichier avec les fonctions• Un header• On écrit un makefile le plus générique possible pour pouvoir le réutiliser.
Pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56