TP7&8: Compilation et makefiles

Les étapes de la compilation

Le pré-processeur

Comme vous l’avez vu au TP précédent, un fichier contenant toutes les sources ce n’est pas très pratique. Imaginez que le code d’un OS (~8*10^6) soit regroupé dans un seul fichier. Cela devient impossible à gérer! Nous allons donc séparer le code en plusieurs fichiers, et c’est ce que vous allez faire avec votre code.

Pour commencer, répartissez le code contenu dans le fichier main.c:

  • mettez les signatures des fonctions (new_link, chain, display_linked_list, free_list, sort, search, insert, read_file_content) dans un fichier linked-list.h
  • mettez la définition des types de données (Link_t et Student_t) dans un fichier link-types.h
  • mettez le code des fonctions (new_link, chain, display_linked_list, free_list, sort, search, insert, read_file_content) dans un fichier linked-list.c
  • laisser le reste du code (définition des variables globales et fonction main) dans le fichier main.c: supprimer les sections de code que vous avez copiées dans d’autres fichiers.

Vous allez maintenant compiler un programme dont le code C a été réparti sur plusieurs fichiers « .c ». Pour la compilation de trois fichiers fic1.c, fic2.c, et fic3.c, vous devrez exécuter la commande:

$gcc -Wall fic1.c fic2.c fic3.c -o mon_prog

L’exécution de cette ligne de commande avec les fichiers main.c et linked-list.c devrait vous renvoyer des erreurs et warning du type:

error: unknown type name 'Link_t'
warning: implicit definition of function 'read_file_content'

L’erreur vient du fait que le type Link_t est utilisé, mais le compilateur ne trouve pas sa définition. Utilisez la directive #include pour remédier à ce type problème.

Le warning vient du fait que la fonction read_file_content est appelée, mais le compilateur ne connait pas la signature de cette fonction.

Exercice 1: Utilisez la directive #include pour remédier à ce type problème.

Maintenant, vous pouvez avoir des erreurs de redéfinition de type. Pour être sûr de mettre en évidence le problème, incluez linked-list.h puis link-types.h dans le fichier linked-list.c.

error: typedef redefinition with different types ('struct Link_t' vs 'struct link')

Ceci est du au fait que vous incluez plusieurs fois le même fichier .h, et donc vous définissez plusieurs fois le même type de données. Tapez la commande suivante pour vous en convaincre:

gcc -E linked-list.c

Exercice 2: Utilisez les directives #ifndef /#endif et #define pour remédier à ce type problème.

Des erreurs de compilation, encore? Et bien oui: vous devriez avoir des erreurs du type:

error: use of undeclared identifier 'number_of_students'

Ceci est dû au fait que lors de la compilation dans linked-list.c, vous utilisez une variable qui n’a pas été déclarée.

Exercice 3: déclarez la variable number_of_students avant de l’utiliser dans le fichier linked-list.c pour remédier à ce type de problèmes. Attention cependant à ne pas DEFINIR cette variable deux fois! (Utilisez le mot clé extern pour vous en assurer).

Normalement, plus d’erreur ni de warning, merci gcc ! 😉

La compilation séparée

Imaginez que le code d’un OS (~8*10^6) soit regroupé dans un seul fichier. Cela devient très (très!) long à compiler! Nous allons donc compiler les fichiers « .c » séparément. et c’est ce que vous allez faire avec votre programme.

Essayez pour commencer:

gcc -Wall linked-list.c

Mille milliards de milles sabord, encore des erreurs de compilation!

Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64

La raison est simple: vous avez essayé de produire un fichier executable à partir d’un fichier « .c » qui ne contient pas d’implémentation de la fonction main. gcc ne sait pas par où commencer l’exécution du programme…

Exercice 4: utiliser l’option -c pour produire des fichiers objets:

  • gcc -Wall -c main.c
    L’effet de cette commande: le fichier main.c est compilé, et un fichier objet, main.o, est produit.
  • gcc -Wall -c linked-list.c
    L’effet de cette commande: le fichier linked-list.c est compilé, et un fichier objet, linked-list.o, est produit.

Exercice 5: vérifiez le format des fichiers objets en utilisant la commande file :

$ file linked-list.o 
foncs.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$ file main.o
main.o:  ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

Exercice 6: visualisez quels sont symboles connus dans les fichiers objets en utilisant la commande nm

$ nm main.o
$ nm linked-list.o

Interprétation du résultat:
T —–> le symbole est défini dans le fichier, c’est une fonction,
B —–> le symbole est défini dans le fichier, c’est une variable globale, non initialisée
C —–> le symbole est défini dans le fichier, c’est une variable globale, initialisée à une valeur différent de 0 à la définition
U —–> le symbole n’est pas défini dans ce fichier, il doit se trouver dans une bibliothèque, ou dans un autre fichier objet.

Vérifier que :

  1. main et number_of_students sont définis dans main.o;
  2. chain, display_linked_list, free_list, insert, etc. sont NON définis dans main.o
  3. chain, display_linked_list, free_list, insert, etc. sont définis dans linked-list.o;
  4. plusieurs symboles ne sont connus dans aucun des deux, par exemple : strcmp, scanf, printf.

L’édition de lien

  • gcc linked-list.o main.o -o linked-list-exe
    L’effet de cette commande: un fichier executable, linked-list-exe, est produit. Vérifiez son bon fonctionnement.

Exercice 7: vérifiez le format du fichier exécutable en utilisant la commande file :

$file linked-list-exe
appli: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs),
for GNU/Linux 2.6.32, BuildID[sha1]=0x294d2160a3f70a1b0467e926454fdaa64a5c5016, not stripped

Exercice 8: visualisez quels sont symboles connus dans les fichiers objets en utilisant la commande nm:

$nm linked-list-exe

La sortie de nm montre que certains symboles ne sont toujours pas définis dans cet exécutable, par exemple: printf et strcmp.

Pour avoir la liste des bibliothèques dites « dynamiques » nécessaires pour exécuter appli, exécuter la commande :

$ ldd linked-list-exe

On obtient une liste du type :

   	linux-vdso.so.1
	libc.so.6 => /lib64/libc.so.6
	/lib64/ld-linux-x86-64.so.2

L’utilitaire strip retire la table des symbole, l’exécutable est plus petitt :

$ ls -l linked-list-exe
-rwx------ 1 domas ir_domas 13572  6 déc.  13:57 linked-list-exe

$ strip linked-list-exe

$ ls -l linked-list-exe
-rwx------ 1 domas ir_domas 13308 6 déc. 13:57 linked-list-exe

Si vous pensez manquer de temps pour finir le TP,  passez directement aux exercices sur les Makefile (plus bas dans cette page).

Par défaut, l’édition de lien est dynamique. Pour réaliser une édition de lien statique, nous devons utiliser l’option -static de gcc.

Création et utilisation de bibliothèque (library en anglais)

Certaines fonctions peuvent être utilisées, ou ré-utilisées par plusieurs program (printf par exemple). Ces fonctions sont alors regroupées dans des bibliothèques.

Une bibliothèque (statique) n’est rien d’autre qu’une archive qui regroupe plusieurs fichier objets en un seul. Pour créer une librairie, on utilisera l’outil « ar » (ar comme archive). La principale utilisation de ar est la construction de bibliothèques. On va reprendre l’exercice précédent et créer une bibliothèque pour y ranger les fonctions se trouvant dans le fichier linked-list.c.

Création d’une bibliothèque

  1. Bien sûr, il faut d’abord créer les fichiers objets, par exemple, ici :
    gcc -Wall -c linked-list.c
  2. Création de l’archive en utilisant la commande ar :
    ar -r libmy.a fic1.o fic2.o
  • L’option -r permet d’inclure les fichiers objets fic1.o et fic2.o dans l’archive nommée libmy.a. Notez que les bibliothèques commencent le plus souvent par le préfix lib.
  • L’option -t affiche la liste des fichiers contenus dans l’archive
$ ar -r liblinked_list_utils.a linked-list.o
ar: creating archive liblinked_list_utils.a

$ ar -t liblinked_list_utils.a
linked-list.o

 

  • Gestion de la bibliothèque.
    • La commande nm permet de vérifier quels sont les symboles définis et indéfinis dans l’archive.
    • Pour ajouter d’autres objets dans une bibliothèque , on utilise la commande ar avec l’option -r.

 

Exercice 9: créez la bibliothèque liblinked_list_utils.a à partir du fichier objet linked-list.o

Nous n’avons plus besoin du fichier objet, et nous supprimons aussi le fichier executable; faites :

$rm linked-list.o linked-list-exe

Utilisation d’une bibliothèque

Vous allez maintenant faire l’édition de lien en utilisant la bibliothèque:

$ gcc main.o liblinked_list_utils.a -o linked-list-exe
  • On peut aussi utiliser l’option -l de l’éditeur de liens pour lui demander d’aller chercher une bibliothèque. Cette option s’utilise ainsi : -lx où x est une abréviation pour libx.a. C’est pour cette raison que nous avons utilisé le nom liblinked_list_utils.a dans l’exemple. On peut donc utiliser cette bibliothèque en faisant :
$gcc main.o -L. -llinked_list_utils -o linked-list-exe
  • Si l’éditeur de liens ne trouve pas la bibliothèque, il faut lui indiquer qu’elle est dans le répertoire courant en utilisant l’option -L (suivi du chemin vers le répertoire contenant la bibliothèque) :
$gcc main.o -L. -llinked_list_utils -o linked-list-exe

Exercice 10: créez l’exécutable linked-list-exe par édition de lien entre main.o et liblinked-list-utils.a

  • On va maintenant mettre la bibliothèque libma_bib.a dans le répertoire lib :
    $mkdir lib
    $mv liblinked_list_utils.a ./lib/liblinked_list_utils.a

    L’option -Lrep_bib de gcc indique qu’il faut chercher les bibliothèques dans le répertoire rep_bib. La commande à exécuter est alors :

    $gcc main.o -L./lib -llinked_list_utils -o appli

Exercice 11: Après avoir déplacé la bibliothèque dans le répertoire lib, re-créez l’exécutable linked-list-exe par édition de lien entre main.o et liblinked-list-utils.a

Informations complémentaires sur les bibliothèques:

Les bibliothèques présentées dans ce TP sont des bibliothèques statiques. Les bibliothèques dynamiques sont construites par gcc (en utilisant l’option -shared). Les bibliothèques dynamique ont une extension « .so » et exposent plus de symboles et d’informations qu’une bibliothèques statique pour pouvoir être chargé dynamiquement.

Certaines bibliothèques, en particulier la « libc », sont ajoutées par défaut lors de l’édition de lien (voir utilisation de la commande ldd plus haut dans ce TP). C’est ainsi que sont résolus les symboles scanf, printf, etc.

Lorsque vous utilisez des opérations mathématiques (e.g. sqrt), vous pouvez utiliser la librairie mathématique. Pour cela, il vous suffit d’utiliser l’option -lm, ce qui signifie que gcc trouvera un fichier libm.a (ou libm.so). Où? … Dans /usr/lib

Makefile

Reprenons l’exemple du code d’un OS (~8*10^6 lignes de code). Ce code devra être réparti dans beaucoup, beaucoup de fichiers (plus de 16 000 pour le noyau linux). Donc nous allons devoir structurer ces fichier, et organiser cette structure en répertoires, sous-répertoires, sous-sous-répertoires, etc.

De plus, il va falloir automatiser la construction de l’exécutable de sorte à ne pas re-compiler 8*10^6 lignes de codes à chaque fois qu’on en modifie quelques unes…

Exercice 12: créez les répertoires src, include, lib réorganiser les fichiers sources comme suit:

  • fichiers .c dans le répertoire src
  • fichiers .h dans le répertoire include
  • fichier .a dans le répertoire lib

Exercice 13: complétez le fichier Makefile en suivant les instructions contenues dans les commentaires de ce fichier. Indication: utilisez l’option -I (I comme Include) de gcc pour indiquer au compilateur où chercher vos fichiers « .h ».

Si vous n’avez pas traité les questions sur la création de librairies, simplifiez le Makefile pour ignorer la partie concernant la création et l’utilisation de la librairieliblinked_list_utils.a.

Pour vérifier que vous avez bien réalisé cet exercice, exécutez les commandes suivantes :

$ make clean

Quel est l’effet de cette commande?

$ make

Quel est l’effet de cette commande?

$ make all

Quel est l’effet de cette commande?

Pour en vérifier le bon fonctionnement du programme exécutable produit, exécutez le:

./linked-list-exe student_list

Exercice 14: Si vous avez bien écrit votre makefile, la bibliothèque linked-list-utils.a devrait être reconstruite si (i) vous modifiez le fichier linked-list.c et (ii) si vous exécutez make. Vérifiez cela.

Pour aller plus loin, faites les exercices qui suivent; il n’est pas obligatoire de finir la partie du TP ci-dessous pour passer aux TP suivant (forks/exec/wait) lors de la prochaine séance.

Exercice 15: Vous allez maintenant vous occuper des dépendances vers les fichiers .h. En effet, si vous modifiez un fichier .h il faut reconstruire les cibles qui dépendent de ces fichiers .h

En l’état, votre solution ne fait pas cela (vous pouvez le vérifier en modifiant un fichier .h et en reconstruisant une cible qui dépend de ce fichier).

Lister ces fichier .h à la main est fastidieux, imaginez que vous ayez à le faire pour un code de plusieurs centaines de fichier, et plusieurs milliers de ligne de code. Ce serait trop pénible de le faire manuellement.

Nous allons donc automatiser cela en utilisant l’option -MM de gcc:

$ gcc -MM src/main.c -I include/
main.o: src/main.c include/link-types.h include/linked-list.h

Inspirez vous du Makefile présenté en cours pour ajouter les dépendances de chaque cible

Exercice 16 (sans solution fournie): pour aller plus loin, nous vous proposons d’utiliser les règles implicites de makefile:

.c.o:
    $(CC) -c $(CFLAGS) -o $@ $<

Questions: avec votre solution, peut-on reconstruire la librairie sans re-compiler les autres sources du programme? Proposez une solution.

Pour information : annexe, illustrée sur un autre exemple que celui du TP

Outil de debug (GDB)

Pour les mises au point de programmes (i.e. le debug), il est conseillé d’utiliser des outils tels que gdb qui permettent de suivre l’exécution du programme en pas à pas et de vérifier ou de modifier le contenu de leurs variables.
Souvent plus efficace que le mise au point faite en utilisant printf !
Si vous ne l’avez pas déja fait, ajoutez l’option -g aux paramétres CFLAGS et LDFLAGS.
Faites make clean puis make all, puis utilisez gdb en vous inspirant de l’exemple ci-dessous.

Exemple :
Pour utiliser gdb sur le programme appli, on entre la commande suivante :

gdb appli

On peut maintenant exécuter ce programme appli sous le contrôle de gdb, et essayer les commandes suivantes :

  • list affiche 10 lignes à partir de la position courante,
  • break positionne un point d’arrêt,
  • run démarre l’exécution
  • continue relance l’exécution jusqu’au point d’arrêt suivant,
  • step fait passer à l’instruction suivante,
  • print visualise la valeur des variables,
  • set permet de modifier la valeur d’une variable,
  • help donne le liste des commandes,

Par exemple :

(gdb) list 25
20		
21		printf ("nom du fichier ?\n");
22		scanf( "%s", le_fichier);
23		
24		nb_mots = lire_fichier(tab_mots, le_fichier);
25		
26		/*-- visualisation du tableau a l ecran  ---*/
27		for (i=0; i<=nb_mots; i++){
28			printf("tab_mots[%d] = %s %d\n", i, tab_mots[i].texte,
29				   tab_mots[i].info);		
(gdb) break 27
Breakpoint 1 at 0x400758: file main.c, line 27.
(gdb) run
Starting program: appli 
nom du fichier ?
main.c
Breakpoint 1, main (argc=1, argv=0x7fffffffde98) at main.c:27
27		for (i=0; i<=nb_mots; i++){

(gdb) print nb_mots
$1 = 98
(gdb) set nb_mots=10
(gdb) print nb_mots
$3 = 10

Structures des fichiers objets et exécutables

Un premier fichier objet

Exécuter la commande suivante :

  • objdump -h main.o
    (si objdump n’existe pas, essayer gobjdump).

On obtient des informations sur les différentes sections du fichier objet main.o, en particulier :

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000148  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000188  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000188  2**2
                  ALLOC
  3 .rodata       00000068  0000000000000000  0000000000000000  00000188  2**0

Commentaires :

  • size et File off : taille de la section et son adresse de début dans le fichier objet,
  • Algn : alignement, en puissance de deux,
  • la section text contient le programme,
  • la section data contient les données modifiables ,
  • la section rodata contient les données non modifiables (read only!), dans notre cas les chaines de caractères des formats utilisés dans les printf.

Exécuter la commande suivante :

  • objdump -t main.o

Résultat :

main.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 main.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000e10       O *COM*	0000000000000020 tab_mots
0000000000000000 g     F .text	0000000000000148 main
0000000000000000         *UND*	0000000000000000 puts
0000000000000000         *UND*	0000000000000000 __isoc99_scanf
0000000000000000         *UND*	0000000000000000 lire_fichier
0000000000000000         *UND*	0000000000000000 printf
0000000000000000         *UND*	0000000000000000 strncmp
0000000000000000         *UND*	0000000000000000 chercher

Commentaires sur le résultat de cette commande :

  • COM : variable globale non initialisée,
  • data : variable globale initialisée,
  • UND : référence non satisfaite,
  • la première colonne indique l’adresse de la variable dans sa section, la troisième sa taille, par exemple :
    0000000000000e10       O *COM*	0000000000000020 tab_mots
    

    veut dire que la variable tab_mots est implantée sur 20 octets à partir de l’adresse e10 dans la zone COM.

Si on exécuter la commande  :

  • objdump -s main.o

On peut voir le contenu de cette section rodata:

Contents of section .rodata:
 0000 6e6f6d20 64752066 69636869 6572203f  nom du fichier ?
 0010 00257300 7461625f 6d6f7473 5b25645d  .%s.tab_mots[%d]
 0020 203d2025 73202564 0a007175 656c206d   = %s %d..quel m
 0030 6f742063 68657263 68657220 3f006669  ot chercher ?.fi
 0040 6e007472 6f757665 20202573 20286d6f  n.trouve  %s (mo
 0050 74202564 2920210a 00706173 2074726f  t %d) !..pas tro
 0060 75766520 25730a00                    uve %s..        

Un second fichier objet

Exécuter la commande suivante :

  • objdump -h foncs.o

On obtient des informations sur les différentes sections du fichier objet foncs.o:

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000139  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  0000017c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  0000017c  2**2
                  ALLOC
  3 .rodata       00000040  0000000000000000  0000000000000000  0000017c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

Exécuter la commande suivante :

  • objdump -t foncs.o

Extrait du résultat :

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 foncs.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     F .text	00000000000000c2 lire_fichier
0000000000000000         *UND*	0000000000000000 fopen
0000000000000000         *UND*	0000000000000000 printf
0000000000000000         *UND*	0000000000000000 exit
0000000000000000         *UND*	0000000000000000 __isoc99_fscanf
00000000000000c2 g     F .text	0000000000000077 chercher
0000000000000000         *UND*	0000000000000000 tab_mots
0000000000000000         *UND*	0000000000000000 strcmp

Commentaires sur le résultat de cette commande :

  • text contient lire_fichier et chercher qui sont UND dans main.o,
  • toutes les autres variables sont UND .

Exécuter la commande suivante :

  • objdump -s foncs.o

Extrait du résultat :

Contents of section .rodata:
 0000 72006c65 20666963 68696572 20257320  r.le fichier %s 
 0010 6e276578 69737465 20706173 0a002573  n'existe pas..%s
 0020 006c6967 6e652020 2564203a 20657272  .ligne  %d : err
 0030 65757220 0a000000 00000000 0000803f  eur ...........?

Commentaires sur le résultat de cette commande :

  • pas de data
  • dans rodata on trouve touts les constantes chaines de caractères des formats utilisés dans les printf.

Le fichier exécutable

Produire un fichier exécutable en utilisant la commande suivante :

gcc main.o foncs.o -o exo

Exécuter ensuite la commande suivante :

  • objdump -h exo

On obtient des informations sur les différentes sections du fichier exécutable exo, on s’intéresse aux suivantes:

Sections:
 14 .rodata       000000b8  0000000000400a30  0000000000400a30  00000a30  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 23 .data         00000004  0000000000600e60  0000000000600e60  00000e60  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00000e30  0000000000600e80  0000000000600e80  00000e64  2**5

Commentaires :

  • la section rodata a une taille égale à la somme de celles de main.o et foncs.o, elle commanece en a30 dans l’exécutable.
  • la section data contient toutes las variables globales initialisées,
  • la section bss contient toutes les variables globales non initialisées.

La pile

  • La pile croit dans le sens des adresses décroissantes.
  • Rôle de %esp et %ebp :
    Les registres 32 bits %esp et %ebp définissent, dans la pile, l’espace de la fenêtre (stack frame) associée à une fonction :
  • %ebp : pointeur sur la base de la pile,
  • %esp : pointeur sur le sommet de la pile, modifié par les instructions pushl, popl, call, ret.
  • Sauver/Restaurer :
    Sauver les valeurs de %esp et %ebp mémorise un contexte dans la pile. Restaurer les valeurs de ces registres permet de se retrouver dans le contexte sauvegardé.
  • Mécanisme push/pull :
    • pushl var : décrémente %esp et copie 4 octets,
    • popl var : copie 4 octets dans var et incrémente %esp,

    Exemple : une stack frame pour 4 variables :

        pushl %ebp              sauvegarde de la base de la pile
        movl  %esp, %ebp        deplacement de la base de la pile
        subl $16, %esp          16 octets dans la nouvelle pile
    
  • call, ret et leave  :
    Les trois instructions spécifiques aux appels de fonction sont:
  • call Fonc
    call est l’enchainement d’un empilement de l’adresse qui la suit et d’un jmp à l’adresse fournie en opérande.
  • leave
    restaure la fenetre de pile précédente, c.a.d :
    movl %ebp,%esp
    popl %ebp
  • ret
    retour de fonction (c.a.d : popl %eip). L’instruction ret combine dépilement et branchement à l’adresse dépilée.
  • valeur de retour  :
    L’éventuelle valeur de retour est rangee dans %eax.
  • Etat de la pile dans la fonction :
         4(%ebp)                adresse de retour
         8(%ebp)                argument 1
        12(%ebp)                argument 2
    	...
    

Génération de code

On donne ici un fichier source écrit en langage C et le code assembleur produit par le compilateur gcc (3.4.2) sur un système Linux/PC Intel.
Remarquer les optimisations pour les cas factorielle(0) et factorielle(1).

/*
 *  factorielle.c
 */
#include 
int main(void){
	int factorielle (int n);
	static int n, i =4;
	n =  factorielle(i);
	printf("Factorielle(%d) = %d\n", i, n);
	return 0;
}

int factorielle(int n){
	int i;
	if  ( (n == 1) || (n == 0) )  i= 1; 
	else i = n * factorielle(n-1);
	return i;
}


Le fichier assembleur (simplifié et commenté) produit par la commande : gcc -Wall -O4 -s factorielle.c

////////////////////////////////////////////////////
	.comm	n.0,4,4
i.1:
	.long	4
.LC0:
	.string	"Factorielle(%d) = %d\n"

	.text
main:
	pushl	%ebp
	movl	%esp, %ebp
	pushl	%ebx
	pushl	%ebx
	andl	$-16, %esp
	movl	i.1, %ebx          mettre i dans ebx
	subl	$16, %esp
	cmpl	$1, %ebx           comparer ebx avec 1
	movl	$1, %eax           mettre 1 dans eax (valeur de retour)
	ja	.L11                   aller a L11 SEULEMENT SI i > 1 
	movl	%eax, n.0          initialiser n
	pushl	%edx
	pushl	%eax               mettre n sur la pile
	pushl	i.1                mettre i sur la pile
	pushl	$.LC0              mettre adresse chaine de carc. sur la pile
	call	printf             optimisation : appel direct a printf
	xorl	%eax, %eax
	movl	-4(%ebp), %ebx
	leave
	ret

.L11:
	leal	-1(%ebx), %eax      decrementer ebx et le mettre dans eax
								(eax est aussi valeur de retour)
	pushl	%eax                mettre eax sur la pile comme argument
	call	factorielle       
	imull	%ebx, %eax
	popl	%ecx
	movl	%eax, n.0
	pushl	%edx
	pushl	%eax                mettre n sur la pile
	pushl	i.1                 mettre i sur la pile
	pushl	$.LC0               mettre adresse chaine de carc. sur la pile
	call	printf
	xorl	%eax, %eax
	movl	-4(%ebp), %ebx
	leave
	ret
	
factorielle:
	pushl	%ebp                  decrementer esp et sauver ebp sur la pile 
	movl	%esp, %ebp            mettre le esp courant dans ebp (nouvelle fenetre)
	pushl	%ebx                  decrementer esp et sauver ebx sur la pile
	movl	8(%ebp), %ebx         ebx = valeur du premier argument dans la
	                              fenetre precedente
	cmpl	$1, %ebx              comparaison avec 1
	movl	$1, %eax              toujours mettre 1 dans eax 
	ja	.L6                   si argument > 1 : aller a L6
	movl	-4(%ebp), %ebx        (1) restaurer ebx 
	leave                         restaurer la fenetre de pile précédente 
	                              (c.a.d movl %ebp,%esp puis popl %ebp)
	ret                           depiler le compeur ordinal sauve (c.a.d : popl %eip)

.L6:
	subl	$12, %esp              decrementer esp de 12 pour preparer une nouvelle fenetre
	leal	-1(%ebx), %eax         decrementer ebx et le mettre dans eax (valeur de retour)
	pushl	%eax                   mettre eax sur la pile comme argument
	call	factorielle            appel avec (n-1) en argument
	imull	%ebx, %eax             ebx a ici la valeur deposee en (1)     
	addl	$16, %esp              faire pointer esp sur la fenetre precedente
	movl	-4(%ebp), %ebx         restaurer ebx
	leave                          restaurer la fenetre de pile précédente
	ret