Qu’est-ce qu’un langage de programmation ?

Vous entendez souvent parler de langages de programmation : HTML, C, Pascal,
assembleur, etc. Mais en quel sens peut-on parler de langage pour un
objet dénué de pensée ?

Le fondement de l’informatique

Imaginez que vous voulez construire une maison. Vous ne commencez pas par vous demander de quelle couleur sera le papier peint ou quels jouets on
mettra dans la chambre des enfants, car pour avoir du papier peint il faut
déjà avoir des murs, et pour avoir des murs il faut avoir des fondations,
et pour avoir des fondations il faut déjà avoir un terrain.

Il en va de même en informatique. Un parallèle qu’il ne faut jamais perdre de
vue, car il démystifie beaucoup de choses : l’informatique ne rompt pas
avec une conception mécanique de la nature, mais se place dans sa
continuité. Le monde des ordinateurs, des fichiers et des logiciels n’est
pas un nouveau monde, un monde virtuel, mais une partie de l’ancien monde
qui a simplement beaucoup enflé.

Mais quel rapport, me direz-vous, entre la grenouille galvanisée et le
pingouin de Linux ? La première est matérielle, le second virtuel ; la
première appartient au monde physique, le second au monde
informatique. Mais j’entends déjà le râle des disciples d’Auguste Comte :
« l’inférieur porte le supérieur ». Eh oui ! car la galvanisation de la
grenouille est précisément ce qui a rendu possible la création de Tux, le
pingouin de Linux. L’informatique repose sur l’électronique, qui elle-même
repose sur l’électricité.

Le langage machine

Inintelligibilité pour l’homme

Votre ordinateur n’est en effet pas si malin que ça, puisqu’il ne parle que
par impulsions électriques et ne comprend que ça. C’est bien là le problème :
nous autres humains ne comprenons pas si couramment les impulsions électriques... La communication avec les ordinateurs nous échapperait ainsi :

Le programme

Un jour des hommes se dirent qu’en écrivant des programmes ils pouvaient gagner du temps. Ainsi, une seule commande permettrait d’en convoquer plusieurs autres, ce qui représente un gain énorme en temps. Qu’est-ce qu’un programme ? C’est une série d’instructions regroupées en une seule, de telle sorte que celle-ci appelle celles-là (comme l’est en théorie un programme politique : « si vous m’élisez, je réaliserai ceci, puis ceci, puis
cela, etc. ; si vous voulez toutes ces choses, il y a donc plus simple que
de les réaliser vous-mêmes, c’est de m’en déléguer la tâche. »).

Le programme est donc un moment central de la constitution de
l’informatique : sans programmes (le pluriel est voulu, car il en faut une
quantité énorme) elle ne serait rien. Seulement, avec les programmes
constitués de 1 et de 0, si l’on y gagne en temps, on n’y gagne que peu en
simplicité. Et c’est pourquoi une grande quantité de langages seront constitués, reposant les uns sur les autres, dans un procès d’approche de l’utilisateur comme terme ultime. Mais procédons par ordre.

L’assembleur comme premier langage de programmation évolué

Nous avons d’abord étudié le langage machine ou
code machine : langage natal du processeur, autrement dit du cerveau même de votre ordinateur, langage austère constitué d’une série d’octets. C’est le seul langage compréhensible directement par la machine. Il est composé de concepts élémentaires : lire, écrire. Tous les langages ultérieurs,
toutes les applications et les tous les programmes sont fondés dessus, et
n’en sont que d’habiles combinaisons ; ce qui n’est déjà pas si mal.

Comment créer un autre langage : tout simplement par détournement d’un autre. Vous n’aimez pas les 1 et les 0 ? Qu’à Dieu ne plaise ! Il vous suffit :

Racine, grand programmeur de vers de la deuxième moitié du XVIIe siècle,
exprimait ainsi ce processus :
J’embrasse mon rival, mais c’est pour l’étouffer.
(Racine, Britannicus, IV, 3, v. 1314)

Le premier langage informatique s’appelait Ada et était un véritable
être humain, dont la fonction était de traduire les
instructions que lui donnait le scientifique Charles Babbage en manipulations de la machine analytique qu’il était en train de construire ; Ada Lovelace, fille du poète Byron, fut ainsi à la fois le premier programmeur et le premier langage de programmation de l’histoire : un langage beaucoup plus récent porte son prénom.

Le paradoxe logique du langage qui s’engendre lui-même

C’est la démarche qui fut adoptée par Dennis Ritchie et Brian Kernighan,
concepteurs du langage C : leur objectif était d’avoir un langage de
programmation à la fois performant et proche de la machine.

Ne vous laissez pas tromper par l’apparente contradiction
logique
d’un compilateur écrit dans son propre langage (le paradoxe fut répété plus récemment avec un autre langage : « Java is written in Java »). N’y voyons pas trop vite un analogon de l’exploit du baron de Munchausen, qui se sort lui-même de l’eau avec son cheval en se tirant par les cheveux... La contradiction disparaît en effet dès que l’on prend en considération le fait que le premier compilateur fut écrit dans un autre langage ; ensuite, les produits compilés de ce premier compilateur font ce qu’ils veulent, même compiler à leur tour d’autres compilateurs.

Les niveaux de programmation

Ce nouveau langage, qui assemble des morceaux de langage
machine pour faire un (petit) pas vers le langage humain, c’est précisément le langage assembleur. Une fois que vous avez compris ce passage du langage machine au langage assembleur, vous avez compris la dialectique des langages informatiques : chacun tue son père (le langage dont il procède) et rêve de coucher avec sa mère (l’utilisateur), et il en est ainsi à chaque génération. C’est ce processus qu’explicite la notion de
niveaux. On parle de langages de bas niveau lorsqu’ils se situent près de l’interface machine, donc de ce qui est quasi inintelligible pour un être humain, et de langages de haut niveau lorsqu’ils tendent de manière infinie vers
l’utilisateur. De manière infinie ? Oui, ou à peu de chose près : avec
les interfaces graphiques, puis la reconnaissance vocale, etc., la
proximité avec l’utilisateur est à la fête !

Niveaux élevés et atavisme

Une fête ? Peut-être pas tant que ça... Imaginons un scénario catastrophe :
un bug dans la programmation du langage assembleur. Ceci supposerait que
tous les langages fondés dessus, donc tous ceux qui à leur tour sont fondés
sur ceux-ci, mais aussi tous les logiciels fondés sur ces derniers, etc. -
tous seraient contaminés par ce même bug. C’est le principe d’atavisme
en hérédité, et comme en informatique il n’y a pas d’exogamie, n’espérez même pas que le gène soit remplacé par un autre plus sain...

Ce scénario n’est pas une simple hypothèse théorique : dans sa
conférence « Reflections on
Trusting Trust
 », Ken Thompson, créateur
d’UNIX avec Dennis Ritchie, se sert d’un exemple vécu pour montrer que
l’on ne peut, en matière de sécurité informatique, faire confiance qu’à
des codes que l’on a soi-même écrits.

Ken Thompson a écrit un
compilateur qui, s’il reconnaissait que le code compilé était le
programme login (qui sert, sur les machines UNIX, à
authentifier l’utilisateur, à lui demander son mot de passe et à en
vérifier la conformité ; c’est donc un programme crucial pour la
sécurité informatique), y ouvrait une porte dérobée (backdoor)
lui permettant de se connecter en usurpant l’identité de n’importe quel
utilisateur.

Thompson est même allé plus loin : si le compilateur qu’il a écrit
repère que l’utilisateur est en train de compiler un nouveau compilateur
(vraisemblablement sans porte dérobée), le bug volontaire se reproduit
dans ce nouveau compilateur. Ainsi, si l’on compile le programme
login avec un compilateur que l’on a soi-même créé, même si le bug est absent du code source (et pour cause ! l’utilisateur ne l’y a
pas mis), il est présent dans le code compilé en langage machine.

Chaque programme d’authentification ou de compilation généré, directement
ou indirectement, à partir de ce compilateur truqué par Thompson en
partagera donc les failles de sécurité, et ce, de façon totalement
invisible pour l’utilisateur (à moins qu’il n’envisage de lire lui-même l’intégralité du code compilé en langage binaire, ce qui relève de l’impossible).

La pyramide des langages

Une même commande peut être créée dans de nombreux programmes : si un langage enfant peut le faire, c’est parce que et seulement parce que le
langage parent le pouvait avant lui ; mais ce que le langage enfant gagne
sur le parent, c’est en général l’accessibilité.

Une commande shell

L’exemple classique est
l’affichage du message "Hello world". Dans un simple shell,
on peut écrire par exemple :

echo "Hello world !"

soit tout simplement une commande pour spécifier à quel type de shell la
commande s’adresse (la première ligne), et la commande en elle-même,
composée du message lui-même (Hello world !) ainsi que de l’instruction
permettant de l’afficher (echo).

La commande, telle que nous l’avons rapportée, se présente de cette
façon avant son exécution. Mais jetez donc un regard à la complexité des
appels systèmes qu’elle génère lors de son exécution !

<strong>meles@philonous ~ $</strong> strace echo
'Hello world !' 1> strace_helloworld 2>&amp;1
execve("/bin/echo", ["echo", "Hello world !"], [/* 66 vars */]) = 0                        
uname({sys="Linux", node="philonous", ...}) = 0                                            
brk(0)                                  = 0x804c000                                        
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40015000  
open("/etc/ld.so.preload", O_RDONLY)    = -1 ENOENT (No such file or directory)            
open("/etc/ld.so.cache", O_RDONLY)      = 3                                                
fstat64(3, {st_mode=S_IFREG|0644, st_size=80662, ...}) = 0                                  
old_mmap(NULL, 80662, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40016000                            
close(3)                                = 0                                                
open("/lib/tls/libc.so.6", O_RDONLY)    = 3                                                
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220O\1"..., 512) = 512            
fstat64(3, {st_mode=S_IFREG|0755, st_size=1165108, ...}) = 0                                
old_mmap(NULL, 1175436, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x4002a000  
old_mmap(0x40143000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x118000) = 0x40143000                                                                        
old_mmap(0x40147000, 8076, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40147000                                                                              
close(3)                                = 0                                                
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40149000

... et ainsi de suite sur environ 80 lignes.. Et encore ! Tout ceci est exprimé en langage à peu près intelligible ; en langage machine, ce serait bien plus long et bien plus complexe...

À travers cet exemple, vous pouvez bien voir comme cela se passe : la
commande (ou le
programme) envoie des instructions au système
d’exploitation
, qui
lui-même les traduit en langage binaire pour le
processeur, qui renvoie
une information en langage binaire que le système
d’exploitation
renvoie sous la forme suivante à
l’écran :

Hello world !

Un programme en C

Imaginons le même programme, mais dans un autre langage, le langage C. Nous écrirons :

             
#include &lt;stdio.h&gt;<br />
#define EXIT_SUCCESS 0<br />  
<br />                        
int main(void)<br />          
{<br />                      
 printf("Hello world !\n");<br />
 return EXIT_SUCCESS; <br />                                              
}                                                                          

Comme vous pouvez le constater, le fichier a grossi par rapport à la
commande simple echo "Hello world !" . Mais n’allez pas
croire que le C rend complexe ce qui est si simple et si
intuitif en script shell ; c’est bien au contraire le script shell qui
simplifie les commandes C. Le shell est en effet écrit, en
général, en C ; ainsi, lorsque vous tapez une commande simple dans le
shell, sachez qu’il ne s’agit que d’un raccourci pour un ensemble de
commandes complexes en C, qui est lui-même un raccourci pour un ensemble de commandes encore plus complexes en langage binaire.

Et ce n’est pas tout ! Car ces lignes complexes ne sont encore que le
fichier tel qu’on l’a écrit ; or, en C (mais ce n’est pas propre au C), pour qu’un programme soit exécutable, il faut le compiler. Qu’est-ce qu’une compilation ? C’est une traduction d’un fichier d’un langage en un autre, de plus bas niveau.

Ce que le programmeur a donc écrit depuis un langage relativement
commode, l’ordinateur le traduit en un autre langage, interprétable
directement par le processeur.

Compilation et interprétation

Cette traduction d’un langage évolué en langage binaire peut avoir lieu
de deux façons différentes :

Les langages de programmation se partagent donc en deux grandes
catégories selon le moment de leur traduction en langage binaire. Parmi
les langages compilés figurent le C, le C++, etc. ; parmi
les langages interprétés, Perl, les scripts shell, etc. Le cas
de Caml est un peu ambigu, car ce langage peut être aussi bien compilé
qu’interprété. L’interprétation est une forme de compilation, mais une compilation à la volée, ce qui suffit à la distinguer de la compilation au sens étroit du terme.

Compilation

Les avantages de la compilation sont :

Ses inconvénients sont :

Interprétation

Les avantages de l’interprétation sont :

Ses inconvénients sont :

L’écart qui sépare la compilation de l’interprétation est exactement
celui qui sépare le langage de programmation du langage binaire : si le
programme compilé, étant en langage machine, est illisible d’une
façon courante pour l’homme, le programme à interpréter est, avant son
interprétation, inintelligible pour le processeur ; ou plutôt, il ne lui
apparaît que comme un simple fichier texte (à ceci près qu’il possède un
attribut d’exécutabilité).

Conclusion

Un langage de programmation est donc l’ensemble des procédures
normalisées
(pour une définition plus précise de ce qu’est une
norme informatique, cf. la page Pourquoi des normes
en informatique ?
conformément auxquelles des commandes
humainement intelligibles sont traduites en langage machine
.

C’est donc la notion de langage de programmation qui porte
l’essentiel de l’appareil informatique, si l’on définit cet
essentiel comme l’accessibilité à l’être humain des ressources que le puissance de calcul de la machine met à sa disposition : les langages de programmation sont la médiation entre l’intellect humain et la puissance de calcul de l’ordinateur, médiation unilatérale car elle se résume au rapport impératif du programme sur l’activité du processeur.

Parler de langages de programmation pour un ordinateur reste
éminemment métaphorique : à proprement parler, une machine ne peut posséder de langage, et si cette machine particulière qu’est l’ordinateur en possède un, ce ne pourrait être que le langage machine, ou langage binaire. Mais par l’intermédiaire de la compilation, ou de l’interprétation qui n’en
est qu’une espèce, est possible la médiation, le passage continu d’un
langage à l’autre.

Le caractère pyramidal des langages de programmation, i.e le fait qu’ils
soient fondés les uns sur les autres, est ainsi ce qui permet
l’élaboration de tâches de plus en plus complexes : les tâches les plus
élémentaires et les plus répétitives sont déléguées au langage lui-même
(i.e au compilateur ou à l’interpréteur), ce qui nous rend disponible
pour des l’implémentation d’opérations plus abstraites et moins
immédiates.

Haut de page