# TDM n°16 – Grands principes de compilation

[i]Le Tuto Du Mercredi [TDM] est une idée qui fut proposée par Ne0tux. Le principe est simple : nous écrivons et postons les Mercredis des tutoriels sur l'Utilisation de la calculatrice, le Transfert, les Graphismes, la Programmation, ou encore la Conception de jeu.[/i]

Aujourd'hui, on va parler de compilation et pourquoi c'est important. :)

Niveau ★ ★ ★ ☆ ☆
[i]Mots-clés: Compilation, C, Édition des liens, Makefile[/i]

En programmation, la compilation est une étape du développement qui se trouve entre l'écriture du programme et son exécution. Selon les langages et les outils qu'on utilise, elle peut ne pas exister du tout, ou au contraire être un étape cruciale où énormément de choses se passent. Sur Planète Casio, on la rencontre surtout quand on écrit des add-ins en C ou C++, et ses messages d'erreur parfois cryptiques laissent plus d'un développeur amateur perplexe. :)

Dans ce tutoriel, je vais vous expliquer les grandes lignes du processus de compilation, avec l'exemple d'un add-in. Je parlerai du compilateur GCC, de l'éditeur de liens, des Makefile, et des rôles qu'ils remplissent. Vous verrez que le code C et les fichiers g1a n'ont rien à voir et que la transformation du premier en le second révèle des mécanismes passionants. :D

[brown][b]Le principe : construire un programme exécutable[/b][/brown]

Lorsque vous lancez un programme sur votre PC ou calculatrice, c'est le processeur qui exécute le code. Mais le processeur ne sait pas lire ou comprendre le code C ; il parle un langage bien à lui qu'on appelle [b]assembleur[/b]. C'est un langage bas-niveau, peu expressif, et avec lequel il est facile d'écrire des programmes faux et proportionnellement difficile d'écrire des programmes justes. :O

En plus de ça, le langage assembleur est différent (voire [i]extrêmement différent[/i]) d'un processeur à l'autre, à cause des variations d'architecture. Donc un programme assembleur ne marche vraiment que pour un seul processeur ! Tous ces facteurs ont poussé les informaticiens ont inventé des langages plus simples à utiliser, et plus puissants, comme le Basic et le C. L'idée est de programmer par étapes :

1. On écrit des programmes en C (par exemple). Comme le C est un langage expressif, le code est plus facile à lire et à écrire, et il y a moins de bugs.
2. On traduit ce code vers de l'assembleur pour notre processeur à l'aide d'un traducteur. Si le traducteur fait bien son boulot, on obtient automatiquement un programme assembleur qui fait pareil que notre code C.
3. On donne le programme assembleur au processeur et tout le monde est content. :)

Le traducteur qui transforme le code C en assembleur s'appelle un [b]compilateur[/b]. C'est un outil indispensable lorsqu'on veut exécuter un programme directement sur le processeur, car généralement on ne veut pas coder en assembleur ! ^^

Il y a des compilateurs de tous poils. GCC sait compiler (entre autres) du C et du C++ vers de l'assembleur pour une large gamme d'architectures. LLVM compile vers un langage intermédiaire qu'il recompile ensuite en assembleur. Le compilateur Haskell compile du code Haskell en du code C puis demande à GCC de finir le travail... les possibilités sont nombreuses. Le seul point commun est que ça traduit des langages de programmation. :)

[brown][b]Le processus complet : assemblage et édition des liens[/b][/brown]

Le processus complet se fait en fait en plusieurs étapes. L'assembleur est non seulement difficile à utiliser, mais se présente également sous forme binaire. Cela signifie que le code assembleur ne peut pas s'afficher sous forme de texte... ni s'écrire facilement. (>_<)

Avant d'inventer les langages de haut niveau, les informaticiens ont donc commencé par inventer des représentations textuelles pour l'assembleur. C'est exactement le même langage mais représenté sous forme de texte. Notez que le processeur ne comprend pas le texte, que le binaire : et donc il faut traduire.

Le programme qui traduit le langage assembleur textuel en langage assembleur binaire s'appelle un [b]assembleur[/b]. Mais pour éviter les confusions, je vais plutôt dire [i]programme d'assemblage[/i]. ^^

Par facilité, le compilateur C produit de l'assembleur texte. Lorsque le compilateur a fini de travailler, on utilise donc le programme d'assemblage pour retransformer le résultat en code binaire. Le schéma complet ressemble à ça :


[center][adimg]TDM-16-compilation.png[/adimg][/center]

Ici, chaque fichier `.c` est un fichier source. Chaque fichier `.s` correspondant est le code assembleur sous forme textuelle après la compilation. Chaque fichier `.o` est le code assembleur binaire associé.

À ce stade, tous les fichiers ont été compilés individuellement, mais il reste encore à les réunir, partager les fonctions et les variables globales, ajouter les bibliothèques, et vérifier que tout y est. Cette étape s'appelle l'édition des liens, et le programme qui la fait s'appelle l'[b]éditeur de liens[/b] ([i]linker[/i] en anglais).

L'édition des liens est un sujet assez compliqué qui mériterait un tutoriel entier à lui tout seul. ^^

[brown][b]En pratique sur la ligne de commande[/b][/brown]

Prenons un fichier de code C innocent, `example.c`. Sous sa forme originale, c'est du texte facile à lire et à comprendre.

[code]% cat example.c
#include <stdio.h>

int main(void)
{
	puts("Hello, World!");
	return 0;
}
[/code]
Compilons-le ensemble avec GCC pour obtenir le fichier `example.s` contenant une version assembleur textuelle du code.

[code]% gcc -S example.c -o example.s -O3
% cat example.s
	.file	"example.c"
	.text
	.section	.rodata.str1.1,"aMS",@progbits,1
.LC0:
	.string	"Hello, World!"
	.section	.text.startup,"ax",@progbits
	.p2align 4
	.globl	main
	.type	main, @function
main:
	subq	$8, %rsp
	leaq	.LC0(%rip), %rdi
	call	puts@PLT
	xorl	%eax, %eax
	addq	$8, %rsp
	ret
	.size	main, .-main
	.ident	"GCC: (GNU) 9.1.0"
	.section	.note.GNU-stack,"",@progbits[/code]
Le code est déjà bien moins avenant ! Et pourtant il fait la même chose, il appelle la fonction `puts()` avec en paramètre un pointeur vers une chaîne de caractère `"Hello, World!"`.

Maintenant, on peut assembler ça en code objet (assembleur sous forme binaire) à l'aide du programme d'assemblage qui s'appelle [brown][b]`as`[/b][/brown]. Une fois cette étape passée, il n'est plus possible d'afficher directement le fichier car ce n'est plus du texte. À la place, on utilise le programme [brown][b]`objdump`[/b][/brown] qui décode le binaire pour nous.

[code]% as -c example.s -o example.o
% objdump -d example.o
(...)
0000000000000000 <main>:
   0:	48 83 ec 08          	sub    $0x8,%rsp
   4:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi
   b:	e8 00 00 00 00       	callq  10 <main+0x10>
  10:	31 c0                	xor    %eax,%eax
  12:	48 83 c4 08          	add    $0x8,%rsp
  16:	c3                   	retq[/code]
Vous voyez qu'on retrouve les mêmes instructions. Cependant le fichier `example.o` contient uniquement le binaire décrit dans la colonne du milieu. C'est au bord de l'illisible pour des humains. ^^

On peut finalement appeler l'éditeur de liens. L'éditeur de liens s'appelle [brown][b]`ld`[/b][/brown] mais il est difficile à invoquer sur la ligne de commande, donc on va s'adresser à GCC qui est capable de l'appeler pour nous avec tous les détails corrects. Ensuite on peut lancer le programme comme voulu. :)

[code]% gcc example.o -o example
% ./example
Hello, World![/code]
Le fichier `example` est un fichier ELF (un format de code binaire) et correspond au fichier `game.elf` sur mon diagramme. Quand on programme sous Linux, le fichier ELF qui est obtenu après l'édition des liens est le dernier maillon de la chaîne.

Sur calculatrice par contre, il y a encore un peu de travail à faire avant d'obtenir un fichier `game.g1a`. Je ne rentre pas dans les détails aujourd'hui car le fichier G1A contient essentiellement la même chose que le fichier ELF.

[brown][b]Recompilation partielle et Makefile[/b][/brown]

Comme vous pouvez le voir sur mon schéma, les fichiers d'un programme C sont tous compilés inviduellement et réunis seulement à la toute fin.

Supposons que j'ai modifié `main.c` et que je veux recompiler mon application. Comme `gui.c` et `map.c` n'ont pas changé, les fichiers `gui.o` et `map.o` sont déjà à jour. Il me suffit de recompiler `main.c` en `main.o` et rappeller l'éditeur de liens pour l'étape finale. Je n'ai donc recompilé qu'un seul des trois fichiers ; ça s'appelle une [b]recompilation partielle[/b].

Ça peut sembler anodin comme ça, car tout recompiler ne serait pas difficile. Mais des gros projets comme Linux ou Firefox peuvent mettre de précieuses minutes (voire des heures parfois...) à compiler. Il est donc important de ne recompiler que ce qui est nécessaire pour gagner du temps ! ;)

Et c'est là que compiler commence à devenir très compliqué. D'abord il y a plusieurs programmes à lancer, ensuite on ne veut les lancer que sur les fichiers qui ont été modifiés depuis la dernière compilation. Et si on en oublie, il n'y aura pas d'erreur mais le programme ne marchera pas...

Comme d'habitude, la solution est de tout automatiser et de laisser l'ordinateur faire. :)

C'est pour accomplir ce travail que des programmes comme [brown][b]`make`[/b][/brown] ont été inventés. Le job de [brown][b]`make`[/b][/brown] est de compiler des applications pour vous simplifier la vie :

• [brown][b]`make`[/b][/brown] sait appeller automatiquement le compilateur, l'assembleur et l'éditeur de liens. Même si généralement on personnalise les commandes dans un fichier appelé "`Makefile`" :)
• [brown][b]`make`[/b][/brown] s'arrange pour ne recompiler que les fichiers qui ont été modifiés depuis la dernière compilation.
• Et [brown][b]`make`[/b][/brown] fait [i]plein[/i] d'autres choses extrêmement utiles.

Le fichier "`Makefile`" contient des instructions pour [brown][b]`make`[/b][/brown], permettant de personnaliser les commandes de compilation ou carrément de l'utiliser pour autre chose (installer les programmes, générer de la documentation, compiler du LaTeX...).

Comme vous pouvez le voir, [brown][b]`make`[/b][/brown] permet de simplifier un travail relativement compliqué, et donc votre vie en tant que développeur. :)

[brown][b]Conclusion[/b][/brown]

La compilation est l'art de [b]traduire des langages de programmation[/b]. Les processeurs ne comprennent que l'assembleur et on veut programmer dans d'autres langages, donc on fait traduire nos programmes vers l'assembleur à notre place.

Le procédé complet de compilation contient plusieurs étapes et se termine par l'[b]édition des liens[/b] qui permet réunir plusieurs fichiers en un seul exécutable.

Comme compiler prend du temps, on aime [b]recompiler uniquement les parties nécessaires[/b] d'un projet pour gagner du temps. Les outils comme [brown][b]`make`[/b][/brown] le font automatiquement et sont extrêmement utiles pour les développeurs.

À la prochaine ! :)

Lire le TDM précédent : [url=https://www.planet-casio.com/Fr/forums/topic15888-1-TDM-15--L-utilisation-de-l-espace-graphique-en-programmation.html]TDM 15- L'utilisation de l'espace graphique en programmation[/url]
[url=https://www.planet-casio.com/Fr/programmation/tutoriels.php?cat=1]Consulter l'ensemble des TDM[/url]
