Beruflich Dokumente
Kultur Dokumente
Institut Galilée
ère
Deug Mias 1 année
2003-2004
Programmation Impérative
Polycopié de cours n° 2
Enseignants
A. Nazarenko et C. Recanati
Table des matières
3.5.1 Introduction...................................................................................................14
2
3 Structure et fonctionnement d’un ordinateur
Nous allons examiner dans ce chapitre le mode de fonctionnement d’un ordinateur au niveau
de l’unité centrale. Le mode de fonctionnement décrit ici s’applique aux ordinateurs possédant
une architecture de type « Von Neumann » (la plupart des ordinateurs actuels). Commençons
au préalable par présenter quelques unités courantes pour mesurer les capacités de traitement
a. Capacité mémoire
L’unité de base en informatique est l’unité binaire ou bit (binary unit). Toutefois, le bit est une
unité d’information trop petite pour servir d’unité de mesure. Les quantités de mémoires dont
dispose un ordinateur se mesure en fait en octets, un octet étant une séquence de 8 bits. On
regroupe également les bits en mots, un mot étant constitué en général de quelques octets. On
parle alors d’architecture 8, 16, 32, 64 ou 128 bits. Une architecture 8 bits est une architecture
où le mot occupe 1 octet. Une architecture 128 bits est une architecture où un mot occupe 16
octets. Les PC actuels par exemple sont des machines 32 bits (i.e. un mot occupe 32 bits, soit
4 octets).
bit
octet 8 bits
Mega-octet Mo 1024 Ko
Giga-octet Go 1024 Mo
Téra-octet To 1024 Go
10
(notez : 1024 = 2 )
b. Fréquence d'horloge
processeur pendant une seconde. Notez que le cycle d'exécution d'une instruction (cf. plus
Cette fréquence se mesure en Hertz (Hz, MHz). Si un processeur possède une fréquence
6
d'horloge de 200 MHz, cela signifie qu'il effectue 200.10 opérations élémentaires par
seconde.
Aujourd’hui les PC ont des fréquences d’horloge entre 500 et 1000 MHz.
La fréquence d'horloge n'est pas une indication suffisante pour évaluer la rapidité d'un
ordinateur : un ordinateur dont la fréquence d'horloge est plus élevée que celle d'un autre peut
néanmoins être plus lent. En effet, la rapidité d'un ordinateur dépend aussi beaucoup de la
vitesse à laquelle circule l'information entre le processeur et les autres composants de l'unité
3
La vitesse d’exécution dépend aussi beaucoup de la nature des opérations considérées : les
opérations sur les réels sont en général bien plus longues à effectuer que les opérations sur les
entiers. On mesure donc, en général, la vitesse d'un ordinateur à l'aide de deux nombres : le
nombre d'opérations entières par seconde et le nombre d'opérations sur les réels par seconde.
L’unité fréquemment utilisée est le flops (comme floating-point operations per second).
c. Débit
Le débit permet de mesurer la quantité de mémoire qu'un dispositif de stockage (disque dur,
L’unité est le baud (= bit/s). On utilise aussi les unités suivantes : octet/s, Mo/s, Go/s.
d. Temps d'accès
Le temps d’accès désigne le temps nécessaire pour accéder à l'information désirée sur un
support de stockage mémoire. Ce temps ne prend pas en compte le transfert des données.
Le temps d'accès moyen d'un disque dur est le temps moyen pour positionner la tête de lecture
Lorsque le temps d’accès aux mots mémoire gérés par la mémoire centrale est identique pour
tous les mots, on parle de mémoire RAM (pour Random Access Memory) – la plus
mémoire RAM varie de 60 à 80 ns. Par extension, la mémoire RAM étant celle utilisée par la
mémoire centrale, est souvent traduite en français par mémoire vive (par opposition à la
mémoire ROM, pour Read Only Memory, qui désigne une mémoire externe uniquement
accessible en lecture, comme celle d’un disque, et qui est qualifiée de mémoire morte).
− L’unité centrale qui assure le stockage et le traitement des données ainsi que le stockage
− L’unité d’échange qui a en charge des communications avec l’extérieur : entrée des
l’ensemble des périphériques relèvent donc de cette notion abstraite d’unité d’échange.
Nous allons voir maintenant plus précisément les différents éléments constituant un
ordinateur, à un niveau plus fin. Le schéma ci-dessous en décrit les différents éléments. Nous
reviendrons plus loin sur ce schéma pour expliquer comment s’exécute une instruction.
4
cycle : -> 1000Mhz
UC (1ns/cycle) bande Capacité : 2Go -> 20Go
Temps d'accès : 1s (séquentiel)
calcul
calcul
Capacité : 650 Mo -> 5Go
CD-ROM
Temps d'accès : 150 ms, <12x
calcul
Débit (1x) : 150 Ko/s
}
cache
Contrôleur
mémoire
Temps d'accès : 15ns (SCSI, EIDE
…)
centrale
Capacité : 16Mo -> 4Go
Temps d'accès : 60 - 80 ns
bus
adresses
interruption
(commandes)
Nous nous intéressons plus particulièrement à l’unité centrale. Comme on le voit sur le
l’unité arithmétique et logique (UAL) et l'unité de commande (UC). L'UAL est la zone
du CPU qui effectue les opérations arithmétiques et logiques (les résultats intermédiaires
sont stockés dans des mémoires appelés registres). L'UC dirige le fonctionnement de
toutes les autres unités (UAL, Mémoire, Entrées/Sorties) en leur fournissant des signaux
de cadence et de commande.
Un bus est un simple câble de n lignes qui permet de transmettre des données (dans les deux
sens) entre le processeur et la mémoire. Un bus permet également de véhiculer des signaux
entre l’Unité Centrale et les périphériques. Il existe trois types de bus : le bus de données qui
sert à transporter les arguments d’une opération, et qui est constitué, pour les processeurs les
plus récents, de 32 à 64 lignes parallèles ; le bus d’adresses qui permet d’identifier la case
mémoire concernée par l’opération en cours (qui est également constitué de 32 à 64 lignes
Les premiers ordinateurs étaient composés d’une unité centrale et de périphériques gérés
directement par l’unité centrale. Des instructions spéciales permettaient de copier des données
d’un périphérique vers un autre ou de/vers la mémoire centrale. L’unité centrale alternait donc
des phases de calculs (additions, soustractions, etc. sur des registres) et des phases dites
d’entrées/sorties (quand des données devaient être copiées ou lues ; par exemple, un
5
programme pour être exécuté doit d’abord être copié d’un disque vers la mémoire centrale).
Or les accès aux périphériques sont très lents (comparés aux temps d’accès en mémoire
centrale) et il faut cependant souvent lire ou écrire sur un disque ou une imprimante. Le
Une première idée pour améliorer cette situation consiste à traiter à part les tâches de lecture
et d’écriture sur les périphériques (disque dur, disquette, imprimante) en les confiant à un
programme spécialisé. On associe alors à chaque périphérique de même type (i.e. utilisant la
même méthode de gestion des données) un contrôleur. Le contrôleur sert d’intermédiaire entre
assure le contrôle.
fichier présent sur le disque D. Cette demande va correspondre à une instruction, exécutée par
l’unité centrale, qui se réduira ici à envoyer une commande sur le bus relié au contrôleur
l’emplacement où il doit être copié en mémoire centrale. Après cet envoi, l’unité centrale
l’instruction et l’exécutera, ce qui pourra nécessiter pour lui plusieurs pas de calculs, pendant
lesquels l’unité centrale pourra continuer d’avancer. Quand le contrôleur a terminé, il peut en
informer l’unité centrale au moyen d’un signal appeler interruption. Il s’agit d’un code
envoyé sur une partie spéciale du bus et recopié dans un registre spéciale de l’unité centrale :
le registre des interruptions. L’unité centrale est ainsi informée que la copie demandée a bien
été effectuée et peut exécuter des instructions utilisant ces données en mémoire centrale.
La situation que nous venons de brosser peut encore être améliorée en évitant que la mémoire
centrale soit la seule mémoire utilisée pour exécuter les instructions. On dispose pour cela de
différents moyens :
- utiliser des mémoires caches. Une mémoire cache sert d’intermédaire entre la
mémoire centrale et l’unité de calcul. D’acccès plus rapide que la mémoire centrale,
elle est utilisée pour faire des copies de données anticipées entre les adresses en
- utiliser des mémoires tampons. Les tampons sont des mémoires auxiliaires dans les
d’écriture sur le périphérique. La mémoire centrale est ainsi moins souvent sollicitée
L’UC s’occupe donc de gérer l’exécution des instructions d’un programme. Elle comprend
une mémoire d’accès très rapide dans laquelle sont stockés les résultats temporaires et les
6
informations de commande. Dans cette mémoire on trouve, entre autres, deux registres
importants :
Les registres de l’UC ne sont pas accessibles aux programmeurs. L’UC contient aussi un
les circuits nécessaires à l’exécution de l’instruction en cours. Cette unité a besoin des signaux
d’une horloge pour enchaîner les commandes. L’horloge est externe à l’unité.
L’UAL contient tous les circuits électroniques qui réalisent effectivement les opérations
division, la négation (inversion des bits) , les opérations logiques (ET, OU, et OU exclusif).
Les opérandes nécessaires pour ces opérations se trouvent dans des registres contenus dans
cette unité (l’un d’eux s’appelle l’accumulateur). Ces registres sont accessibles aux
programmeurs.
Données et instructions sont stockées comme des suites de bit (0 ou 1) dans des mots-
mémoires (généralement de 32 et 64 bits). Chaque mot-mémoire est repéré par une adresse
unique. Les opérations possibles dans la mémoire centrale sont la lecture et l’écriture de
mots-mémoire.
prochaine instruction,
4) L'opération est exécutée (le contenu d'un ou plusieurs registres est modifié),
7
Bus Mémoire Centrale
d’adresses
CO 5
UAL 0 11
1 23
R0
2 45
R1
Bus de 3 13
Décodage
données 4 3
instruction
Horloge
9
2. Le contenu du mot mémoire d’adresse 5 (l'instruction "Copie mot 1 dans R0") est
l'opération à exécuter,
registre R0,
8
2. Le contenu du mot mémoire d’adresse 6 (l'instruction "multiplie R0 par 2") est
l'opération à exécuter,
2. Le contenu du mot mémoire d’adresse 7 (l'instruction "copie R0 dans case n°0") est
l'opération à exécuter,
d'écriture,
Une fois ces trois cycles exécutés, le contenu des différents registres et de la mémoire centrale
9
Mémoire Centrale
Bus
d’adresses
CO 8
UAL 0 46
1 23
R0 46
2 45
R1
3 13
Bus de
Décodage
données 4 3
instruction
6 Multiplie R0 par 2
Horloge
9
Etat de l'Unité centrale et de la mémoire centrale après exécution des 3 instructions contenues en
mémoire centrale.
Comme les entiers et les caractères, les instructions doivent être représentées sous forme de
chaînes de bits.
- le code opération : il détermine l’action que doit effectuer le processeur. Par exemple,
une addition avec le contenu de l’accumulateur dans un microprocesseur Intel 8086 est
La taille d’une instruction (i.e. le nombre de bits qu’elle occupe en mémoire) dépend de
Ce sont les instructions qui permettent des transfert de données entre les registres et la
mémoire. Il s’agit d’écriture (des registres vers la mémoire) ou de lecture (de la mémoire vers
un registre). On les appelle affectations, car elles permettent d’affecter (i.e. d’attribuer) une
10
Les instructions arithmétiques et logiques :
Ce type d’instruction porte sur un registre donné. Elles permettent d’effectuer une opération
entre le contenu du registre et une donnée, et place le résultat dans le registre concerné. Il y a
gauche.
Ces instructions permettent d’enchaîner un nouveau cycle sur une instruction non consécutive
suivante n’est effectué que si une condition particulière est satisfaite. Par exemple, on
Sans ce type d’instructions, un programme devrait toujours effectuer les mêmes séquences
Le langage assembleur est très proche du langage utilisé par la machine (ou langage machine)
et dépend du processeur. Les instructions données en langage machine sont des suites de 0 et
de 1 a priori illisibles pour un humain normalement constitué. Un premier pas serait de noter
les suite de bits avec la notation hexadécimale (pour séparer plus facilement les octets), mais
Voici à quoi peuit ressembler un programme écrit (sous forme hexadécimale) en langage
machine :
A1 01 10 03 01 12 A3 01 14 ...
C’est pourquoi les langages assembleurs ont été définis. Ils permettent essentiellement de
noter les instructions par des noms explicites suivis de paramètres. Les différents registres
Cette notation est facile à mémoriser : MOV est l’abréviation de move (déplacer), AX désigne
le registre de même nom, et la notation entre crochet indique qu’on fait référence à une
adresse mémoire.
11
Toutes les instructions du jeu d’instructions du processeur ont ainsi une notation symbolique
associée, fournie par le fabriquant du processeur. Ecrire en langage assembleur consiste donc
à écrire des instructions machine dans une notation symbolique, plus facile à appréhender
pour un être humain. Ces instructions seront d’abord stockées dans un fichier texte (constitué
Mettre le contenu de
A3 01 14 MOV [0110], Acc
l’accumulateur à l’adresse 0114
Nous allons illustrer le codage des instructions par un exemple de langage très simple, le
langage MAMIAS.
• les instructions et les données sont codées dans des mots mémoire de 8 bits,
• chaque mot mémoire est repéré par son adresse, à savoir un entier non signé n (n ≥ 0) codé
sur 5 bits ; le mot mémoire d’adresse n est noté par son adresse entre parenthèses, i.e. (n),
• il existe un registre (mot mémoire particulier) appelé l’accumulateur et désigné par Acc,
• une instruction est codée sur un mot mémoire (8 bits).; il s’agit d’un couple (code,
argument), où le code (constitué des 3 premiers bits) désigne une opération, et l’argument
(les 5 derniers bits) désigne un entier (qui représente, selon l’opération, soit une donnée,
• une donnée est un entier signé codé sur un mot mémoire (8 bits).
Le langage MAMIAS permet de coder les instructions données à la machine, et nous avons
comme nous l’avions déjà vu, exécution en boucle d’un cycle du type:
exécuter
4. L'opération est exécutée (le contenu d'un ou plusieurs registres est modifié),
12
6. Fin du cycle d'exécution et démarrage d'un nouveau cycle.
13
Les opérations élémentaires sont :
000 INIT x Acc ← x L’entier signé x codé sur 5 bits, est traduit en un entier
CHARGE n Acc ← (n) Le contenu du mot mémoire d‘adresse n est recopié dans
l’accumulateur
L’opérateur de conjonction bit à bit est défini sur chaque bit par
101 ADD n (n) ← Acc + (n) Ajoute au contenu de la mémoire d’adresse n celui
111 STOP Arrête l’exécution. Les 5 derniers bits n’ont aucune signification.
Mémoire Centrale
CO 00101
00000 00001011
00001 00010111
ACC
00010 00101101
00011 00001101
00100 00000011
00101 00100001
00110 11000001
00111 01000000
01000
01001
14
3.5 Le système d'exploitation
3.5.1 Introduction
``système d'exploitation'' (SE en français, OS pour Operating System en anglais). On peut dire
approximativement que c'est la couche logicielle qui se situe entre le matériel et l'utilisateur
permet aussi la gestions des programmes et des données sous forme de fichiers.
Nous avons vu, dans la section précédente, comment l'unité centrale exécutait un programme
qui lui était fourni en mémoire centrale : la suite d'instructions en langage machine (en code
binaire) est exécutée instruction après instruction, par chargement de l'instruction courante
ordinal. Reste à savoir comment amener le programme que l'utilisateur cherche à exécuter en
l'utilisateur le prochain programme à exécuter (en fait, on verra que l'on peut être plus
programme dont le rôle est d'amener en mémoire centrale et d'exécuter les programmes à la
demande de l'utilisateur. Ces programmes, dont l'exécution est contrôlée par le système
- DOS et ses variantes MS-DOS (Microsoft) et PC-DOS (IBM). DOS est un acronyme
pour Disk Operating System. Il s'agit d'un système d'exploitation dont l’interface
que le système sait exécuter (MKDIR rep par exemple). Datant de 1978, DOS a été le
- Unix et ses variantes, Linux, etc. C'est le premier vrai système multi-utilisateurs. Il a
été conçu dans les années 70, en même temps que le langage C. Son noyau, écrit en C,
a relativement peu évolué, et tous les systèmes actuels s'en inspirent. Tous les gros
système graphique utilisant souris, icônes et fenêtres (notions proposées au départ par
système à interface graphique, qui a remplacé peu à peu le DOS sur les PC. Le noyau
indépendante du DOS.
On peut caractériser les systèmes d'exploitation par la manière dont ils conçoivent la gestion
des tâches et la gestion des utilisateurs. Les systèmes d’exploitation modernes sont des
15
a. La multi-programmation
On dit d’une machine qu’elle est multi-programmée si plusieurs programmes peuvent être
chargés en mémoire et partager l’unité centrale. L’objectif est d’éviter à celle-ci des temps
morts, c’est-à-dire de rester inactive quand le programme en cours d’exécution est par
- Il n'y a qu'un seul utilisateur (premiers systèmes DOS). Ce cas ne se présente plus
- Seul le temps d'exécution compte et pas le temps d'attente (cas de gros programmes).
Cette situation se présente dans le mode de calcul « temps réel » de certains systèmes
en mémoire centrale.
- Il faut assurer le trafic ordonné et sans mélange des données entre la mémoire centrale
b. La multi-utilisation
Les systèmes à temps partagé ou multi-utilisateurs partagent les ressources de traitement entre
pratique le temps CPU est divisé en petites portions, attribuées à tour de rôle aux différents
utilisateurs, qui ont de la sorte l’illusion d’avoir la machine pour eux seuls. Cette technique
présente l'avantage d'utiliser le plus efficacement possible une machine, avec l'inconvénient
A l’inverse, un système est dit mono-utilisateur si, à un instant donné, un seul utilisateur peut
système Unix est un système multi-utilisateur. Avec Unix, plusieurs personnes peuvent être
d'exécuter différentes commandes. Bien entendu, il est impératif dans ce cas que le système
Dans la pratique les systèmes actuels combinent des traitements batch (non interactifs) et des
traitements en temps partagés que les utilisateurs peuvent interrompre ou suspendre à tout
moment.
16
Dans les systèmes à temps partagés, le temps de réponse est un élément clef. Il faut trouver un
parallèle.
- Une machine virtuelle conviviale : l’utilisateur n’a pas besoin de savoir comment
constitue de ce fait une machine virtuelle pour l’utilisateur. Celle-ci doit être la plus
matériel.
D’un point de vue plus technique, pour remplir les deux grandes fonctions précédentes, un
travail, etc.
- Il spécifie comment s'effectue la gestion des processus (les programmes exécutés par
l’ordinateur).
- Il spécifie une méthode de gestion des fichiers, et plus généralement des disques durs,
du clavier, de l'écran ou de tout type de périphériques. Nous n'aborderons plus loin que
la gestion des fichiers. La gestion des différents périphériques nécessite en effet une
- Il permet de définir une gestion des utilisateurs - ce qui suppose de définir qui peut se
connecter sur la machine, avec quel mot de passe et avec quels droits. Cela impose
utilisateurs.
Il existe différents moyens logiciels ou matériels pour mettre en place des procédures plus ou
moins efficaces pour chacun des points précédents. Malheureusement, plus les outils
correspondants sont sophistiqués, plus ces outils sont complexes à mettre en œuvre (la place
occupée par les outils du système d'exploitation en mémoire centrale devient importante et
17
nécessite alors des ordinateurs puissants). Les systèmes développés réalisent donc toujours un
La structure d’un système d’exploitation peut être organisée en une série de couches
fonctionnelles séparant le matériel des utilisateurs, chaque couche de niveau supérieur faisant
appel aux couches de niveaux inférieurs. Elle peut être représentée par le schéma suivant :
Utilisateurs
Programmes
d’applications
(Planification du travail)
Gestion de la mémoire
Noyau
Matériel
comme suit :
- attente d'une commande faite par l'utilisateur demandant à ce qu'un programme soit
environnement CDE sous système Unix), ou par la frappe au clavier d’un nom de
commande sous une interface alpha-numérique (console Unix ou DOS par exemple),
35000 et 36000 par exemple). Mais l’adresse de début n’est pas connue au moment de
comme si cette adresse de début était l’adresse 0. Toutes les adresses du programmes
18
sont donc relatives et ce n’est que lors de l’exécution du programme qu’un calcul
d’adresse réelle est effectué, avant l’exécution d’une instruction proprement dite.
en cours d’exécution. Plusieurs processus peuvent être des exemplaires du même programme
chargés à des adresses différentes de la mémoire centrale et donnant lieu à des exécutions
différentes.
Les systèmes dits multi-programmés, même avec une seule unité centrale, autorisent plusieurs
processus à effectuer des calculs simultanément (c’est-à-dire au même moment). Avec un seul
Mais, dès lors qu'un processus est une suite d'instructions, on peut imaginer « entrelacer » les
différentes suites d'instructions à exécuter. C'est justement ce que les systèmes actuels
permettent : le système exécute pendant un certain temps un processus, puis passe d'un
processus à un autre (il sauvegarde les informations qui permettront la reprise du processus
jusqu'à ce qu'il n'y ait plus de processus à exécuter. Cette opération s'appelle la commutation
de contextes.
L'avantage principal d'une telle méthode est double : s'il y a plusieurs utilisateurs, le système
fait croire à chacun d’eux que la machine est en train de travailler « pour lui », et quand un
processus est interrompu parce qu'en attente de données, le système peut continuer néanmoins
à travailler en exécutant un autre processus. On peut dès lors imaginer avoir l’exécution « en
même temps » de plusieurs processus. Ce que l'on suggère pour l'exécution d'un ensemble de
processus s'applique évidemment aussi à un ensemble de tâches (petits modules séparés d’un
œuvre d'une telle technique alourdit bien entendu considérablement la tâche du système
d'exploitation, qui doit caractériser les processus selon leurs états, et intégrer de nouveaux
Les processus alternent en permanence entre l'état « en exécution » et des états d’attente. Un
seul processus est dans l'état « en exécution », tous les autres étant en attente d’exécution. Le
préemption. Un processus alterne donc continuellement entre différents états, jusqu'à ce que
son exécution soit terminée. Cette gestion de l’exécution fragmentée des processus est gérée
elle-même par deux programmes : l’ordonnanceur (qui détermine dans quel ordre les
processus seront exécutés) et le scheduler (qui détermine le temps de calcul alloué à chaque
exécution). Le noyau du système d’exploitation exécute lui-même ces programmes, qui donne
Un processus doit être placé en mémoire centrale pour pouvoir être exécuté. La zone occupée
par un processus peut être grande et doit être conservée pendant un temps plus ou moins long.
Or la taille de la mémoire centrale est limitée. Il est dès lors indispensable de gérer au mieux
l'allocation de la mémoire nécessaire. Comme toutes les zones n’ont pas à être nécessairement
en mémoire centrale au même moment, le système d’exploitation peut décider d’en mettre
19
une partie sur un disque dur dans une zone spéciale (appelée zone de swap). Quand le système
doit accéder à cette zone, il la charge alors en mémoire centrale. Ce système revient donc à
avoir une mémoire centrale virtuellement plus grande qu'elle n’est en réalité. Bien entendu, en
procédant ainsi, on perd un peu de temps pour recopier la zone de swap (sur disque) en
20
4 Utilisation de l'ordinateur : logiciels
Nous avons vu, dans les chapitres précédents, comment l'ordinateur traitait physiquement (ou
presque) les instructions, telles qu'elles étaient données sous forme de suite de codes binaires
en mémoire centrale. Sur les premiers ordinateurs, chaque programme binaire était implanté
copié à partir d'un disque (disque dur, disquette, CD-ROM) vers la mémoire centrale où il est
exécuté.
Cette description permet de comprendre comment l’exécution des programmes est gérée par
le système d’exploitation, mais n’éclaire pas sur la façon dont l’utilisateur pourra de son côté,
exploiter sa machine. S’il n’est pas informaticien, l’utilisateur se contentera de lancer des
programmes lui permettant d’éditer des textes ou de réaliser diverses tâches qui l’intéresse,
car les programmes, et plus généralement les logiciels, répondent à des problèmes particuliers.
- le système d'exploitation,
La démarche qu'il faut adopter pour concevoir un logiciel peut se décomposer en plusieurs
étapes :
du logiciel), ou le problème que l’on cherche à résoudre. Il faut définir tous les éléments :
les données d’entrées, les résultats (les sorties), les différents élements qu’il faut
représenter pour modéliser la situation ou résoudre le problème, leurs structures (les types
préoccupe pas nécessairement de l'ordre dans lequel les opérations seront exécutées, ni de
la manière dont les fonctionnalités souhaitées seront déclenchées. C'est une première étape
d' analyse.
− La deuxième étape consiste, dans le cas d’une résolution de problème, à trouver une
manière systématique de procéder pour aboutir à une solution. Cela conduit généralement
21
de fonctionnalités, il faudra alors réfléchir à un algorithme permettant de présenter à
syntaxe très strictes et ne présente pas les ambigu tés des langues dites naturelles. On
ï
obtient alors un ou plusieurs modules de programmes (le code source du logiciel). Ces
programmes sont écrits sous forme de textes (à l’aide d’un éditeur de texte ou de
programme, puis tester le code obtenu. Le code s’ontiendra à partir d’un ou des programmes
source en une suite d'instructions exécutables par le processeur. On passe d'un code source
écrit dans plusieurs fichiers à un code binaire exécutable (on dit encore programme exécutable
ou simplement exécutable) placé dans un fichier unique. Cette étape est effectuée par un
compilation n'existe pas car on donne directement le texte du programme source à interpréter.
catégorie. Chaque langage possède une syntaxe et des règles qui lui sont propres.
- Langage machine : les instructions, les données sont codées en binaire. Ces codes
pour un processeur P ne pourra pas, en général, être exécuté sur un autre processeur.
(SAUTE, STOP, DEC, etc.). Un langage d'assemblage n'est qu'une autre façon, plus
- Langages évolués : les langages d'assemblage et les langages machine ont de gros
inconvénients. Ils sont difficiles à suivre, peu compréhensibles, peu puissants (les
nécessite de presque tout réécrire). Un langage évolué permet de ne pas faire référence
− Langages impératifs : Pascal, C, Basic, FORTRAN, Cobol, Ada, etc. Ce sont les premiers
algorithme en terme d’instructions devant être exécutées les unes après les autres, comme
on exécute des ordres. Une instruction est ici un ordre donné à la machine.
22
L’accent est mis sur les algorithmes, le code étant répartis entre différentes procédures. Les
structures de données ne sont pas considérées comme centrales mais ce sont elles qui
Fortran est le premier langage algorithmique. Il est principalement utilisé pour le calcul
scientifique. Cobol est le langage destiné aux applications de gestion : c’est le langage le
plus utilisé dans le monde. Basic est un langage très rudimentaire initialement développé
dans un but didactique. Aujourd’hui, les micro-ordinateurs ont des capacités suffisantes
pour supporter des langages plus évolués. Pascal est essentiellement destiné à
Le langage C est un langage qui a été développé par les laboratoires Bell. Il est orienté vers
Ces langages reposent sur la notion de fonction. On formule les choses en termes de
fonctions, et on peut même parfois définir des fonctions dont les arguments sont eux-
mêmes des fonctions. List (LISt Processing) est un langage fonctionnel qui a été conçu
permettent de récupérer le premier élément d’une liste, et d’en manipuler la fin. Il a connu
Prolog est un langage original un peu à part, inventé par un français : Colmerauer. Il
reprend certains concepts de LISP, mais est avant tout basé sur la notion d’inférence
réutilisation. Les données et les fonctions sont encapsulées à l’intérieur des objets, et les
objets sont organisés par classes d’objets et ensembles de classes d’objets. Dans cette
comme constitué d’objets typés, fournissant chacun des données et les méthodes
SmallTalk est le premier langage orienté objet. Il a jeté les bases de la programmation
objet. C++ est un successeur de C qui incorpore les concepts de la programmation objet
tout en gardant des caractéristiques du langage C. C’est un langage très efficace, qui
connait aujourd’hui un essor industriel. Java est inspiré de C et de C++. C’est le langage
nombre important de librairies, et assure la portabilité des programmes Java d’une machine
à l’autre.
Tout programme écrit dans un langage évolué doit être traduit dans du langage machine pour
pouvoir être exécuté. Il existe deux manières traditionnelles d’effectuer cette traduction :
exécutable stocké dans un fichier. L'exécution est alors faite directement à partir des
23
une suite d'instructions équivalente compréhensible par le processeur. L’interprétation est
effectuée instruction par instruction. Elle est plus lente que la compilation mais permet
Une des originalités du langage Java est d’avoir imaginé un procédé intermédiaire entre la
compilation et l’interprétation. Les programmes sources Java sont compilés dans un langage
binaire appelé pseudo-code destiné à une machine abstraite imaginaire, appelée machine
virtuelle Java. Sur chaque machine réelle particulière tourne alors un programme simulant la
Un avantage annexe non négligeable est une plus grande portabilité des programmes sources
qui nécessitent souvent des retouches particulières pour être compilés sur certaines machines.
Un compilateur est avant tout un programme : c'est lui qui permet de traduire
automatiquement le texte source d'un programme écrit dans un langage évolué donné vers un
évolué en langage machine. Le résultat peut être non exécutable (car incomplet), et on
− Lier plusieurs fichiers objets pour obtenir un unique fichier objet ou un fichier exécutable.
− Vérifier la syntaxe des programmes : l’ordinateur ne tolère aucun écart par rapport à la
syntaxe, même si le sens du programme semble évident. S’il y a des fautes de syntaxe, le
compilateur ne produira pas de code exécutable, mais signalera le type des erreurs
détectées.
sera exécuté.
Outre ces fonctionnalités que l'on retrouve dans tout compilateur, on peut avoir d'autres
c'est-à-dire de montrer instruction après instruction comment la mémoire est modifiée (cf
vérifier si ce qui est écrit est syntaxiquement correct, bien défini, au moment même de
l’écriture du programme source. Les erreurs de syntaxes évidentes pourront ainsi être
24
Ces différentes fonctionnalités peuvent être accessibles au sein d’un même programme qui
VisualC++.
Le logiciel fourni ne comprend pas que le programme du compilateur. Il est livré sous la
pouvant également être lancés séparemment, comme une commande pour réaliser
− Des librairies contenant le code des fonctions définies par le logiciel de compilation, et
informations sur les derniers fichiers compilés, sur la forme, la couleur, l'emplacement de
la fenêtre de compilation.
− Des fichiers de documentation. Ils contiennent la documentation non présente dans l'aide
en ligne.
− Des fichiers d' aide. Ces fichiers contiennent des textes accessibles directement à partir de
l'exécutable.
La compilation d'un programme source (écrit dans un langage évolué) vers un langage cible
(le langage machine ou un langage d’assemblage par exemple) s'effectue en plusieurs étapes.
chaque variable doit correspondre une zone mémoire (une cellule ou un ensemble
consécutif de cellules) contenant la valeur (ou la suite des valeurs) que prendra la variable.
Référencer une variable dans le source, c'est accéder à la cellule associée dans le code.
− Remplacer une à une chaque instruction du programme source par le code correspondant
dans le langage cible. En effet à chaque instruction du langage évolué correspond une
25
Si le langage cible est le langage machine, le fichier exécutable est destiné à être placé dans
une zone de la mémoire centrale pour y être directement exécuté. Cette zone sera
généralement constituée d'une zone de code (les instructions proprement dites), d'une zone de
nécessaire à la traduction des instructions du langage source. Il existe aussi une zone de pile
destinée à stocker des paramètres pour l’exécution de fonctions que l'on n’expliquera pas ici.
Code du programme
Pile
d’exécution
Données auxiliaires
Données
Indiquons au passage que lorsqu’une instruction du programme cherche à accéder à une zone
qu’elle n’est pas censé utiliser (comme la pile d’exécution), l’exécution du programme est
interrompu par un signal d’erreur. Ainsi, avec le langage C, on aura le message d’erreur :
« memory fault - core dumped ». Ce qui signifie qu’il y a eu tentative de lire ou d’écrire dans
une zone interdite, et que le contenu de la mémoire centrale concernant le programme a été
« vidée » dans un fichier intitulé core. Ce fichier pourra être utilisé par un débogueur pour
mémoire.
On souhaite pouvoir écrire de petits programmes, traduisibles en MAMIAS, mais écrits dans
1. Avoir une syntaxe plus agréable, qui aide à mémoriser ce que fait l’instruction.
4. S’abstraire des numéros des mémoires en y faisant référence par des noms : les
variables.
On va montrer ici comment chacun des points précédents peut être réalisés, en définissant un
nouveau langage, compilable en MAMIAS, dont la syntaxe se rapproche de celle des langages
impératifs. Nous essayerons alors de montrer que le langage obtenu est bien compilable en
26
Point 1 : pour réaliser ce premier point, on peut utiliser la notation symbolique que nous
MAMIAS. Il ne s’agit que d’un changement de notation, et les instructions restent les mêmes.
Instruction Signification
INIT x Acc ←x
CHARGE n Acc ← (n)
RANGE n (n) ← Acc
ET n Acc ← Acc et (n)
SAUTE n si Acc = 0 aller a n
STOP Stop
La traduction du nouveau langage est très simple à réaliser puisqu’il ne s’agit au fond que
effectuer la traduction, instruction par instruction. Seul le cas du décalage demande une
attention particulière, car il nécessite un calcul (la valeur absolue de x) lorsque x est négatif.
que des mémoires, et ne font pas référence à l’accumulateur. Par exemple, au lieu d’avoir
pouvoir initialiser directement la valeur contenue dans une mémoire, et disposer, par exemple,
d’une instruction notée (n ) ← x, qui place dans la mémoire (n) la valeur x et permet donc
De même, au lieu d’avoir une addition qui fasse l’addition du contenu d’une mémoire (n)
avec celui de l’accumulateur, on aimerait avoir une instruction qui permette de réaliser l’ajout
d’une valeur x au contenu d’une mémoire (n) et place ensuite le résultat dans la mémoire (n).
le ET bit-à-bit du contenu d’une mémoire (n) avec celui d’une mémoire (p), et retrouver le
résultat dans (n) (ou dans (p)) par exemple. Pour les décalages, on aimerait pouvoir décaler
directement les bits d’une mémoire donnée, vers la gauche ou vers la droite.
Les tableaux suivants montrent que ces nouvelles instructions peuvent effectivement être
27
Nouvelle instruction Traduction MAMIAS Signification
plusieurs instructions MAMIAS. (Pour simplifier le problème, nous supposerons pour la suite
que la traduction d’une instruction du nouveau langage s’effectue sur une seule ligne).
On peut imaginer avoir accès à d’autres instructions dans ce petit langage, comme par
exemple (n) ← (p) ou (n) ← (p) + (q) , ou encore (n) ← (p) + x. Ces exemples pourront être
traités en exercice.
Ainsi par exemple, on pourrait disposer d’une instruction de saut permettant de tester si le
contenu d’une mémoire est nul, afin d’effectuer ensuite un branchement, soit vers une
instruction 1, soit vers une instruction 2. Cette nouvelle instruction, notée par exemple
si (n)=0
<intruction 1>
sinon
<instruction 2>
finsi
conditionnel qui testerait si le contenu d’une mémoire est différent de zéro, ou s’il est positif.
28
Plus intéressant encore, on peut introduire une notion de boucle, avec des intructions
complexes comme
<instruction>
finpour
ou encore
<instruction>
fintantque
La première instruction, l’instruction « pour (m) variant de 1 à j», exécute j fois de suite
l’instruction qui suit, mais dans un contexte à chaque fois différent : la première fois, la
mémoire (m) est initialisée à 1, et l’instruction est exécutée dans ce contexte, puis le contenu
de (m) est augmenté de 1, l’instruction a nouveau exécutée, etc. La mémoire (m) prend donc
successivement les valeurs 1, 2, etc., jusqu’à j, et pour chacune de ces valeurs, une exécution
instructions ne s’exécutent pas toutes dans le même contexte, car elles sont entrelacées de
suit, en testant au préalable si la valeur de la mémoire (m) est non nulle. Tant que cette valeur
est non nulle, on exécute une nouvelle fois l’instruction. Lorsque la valeur de (m) est nulle, on
considère que l’instruction « tantque (m) ≠ 0 » est terminée, et on n’exécute pas l’instruction
interne. Cette instruction « tantque (m) ≠ 0 » peut déclencher une infinité d’exécutions de
l’instruction si la mémoire (m) n’est pas modifiée par cette instruction, et si elle est différente
On peut ensuite facilement étendre cette boucle « tantque (m) ≠ 0 » à l’exécution d’une série
de P instructions :
29
Nouvelle instruction Traduction en MAMIAS Signification
<instruction P> . . . . . . . . .
On voit que l’on peut étendre à loisir le nouveau langage avec des instructions plus complexes
initialement destinés à une simple instruction. C’est la démarche qu’ont suivie les langages de
Quant au dernier point, l’introduction de noms pour remplacer les mémoires, il est très facile à
réaliser et fondamental pour avoir un langage évolué digne de ce nom. On peut en effet
utiliser des noms quelconques pour désigner les adresses mémoires. Ces noms, ou variables
un certain ordre dans le texte du programme. Pour attribuer automatiquement une adresse à
une variable, on peut procéder de la façon suivante : puisque les variables apparaissent dans
un certain ordre, on peut convenir que la première variable qui apparaît dans le texte du
par ordre d’apparition des variables dans le texte du programme. On construit ainsi une table,
appelée traditionnellement table des symboles, qui indique la correspondance univoque entre
les noms (les variables) et les mémoires. Il suffira ensuite d’utiliser cette table pour traduire
Par exemple, le programme suivant, écrit dans notre petit langage évolué :
x ←0
i ←1
j ← p
tantque j ≠0
x ←x+i
i ←x+i
j ← j + -1
fintanque
variable mémoire
x (31)
i (30)
j (29)
30
en
(31) ←0
(30) ← 1
(29) ← p
tantque (29) ≠0
(31) ← (31) + (30)
(30) ← (29) + (30)
(29) ← (29) + -1
fintantque
Le nouveau texte obtenu peut être compilé ensuite à l’aide des tables de traduction
précédentes.
Mais nous n’avons fait ici qu’esquisser une méthode possible de compilation qui utiliserait
ces tables. La définir véritablement présente encore quelques difficultés. Une première chose
à faire sera d’adapter ces tables pour rendre compte du fait que (et c’est le cas général) la
traduction d’une instruction nécessite plusieurs lignes. Une autre difficulté est que les adresses
des instructions données par nos tables de traduction sont, d’une part relatives (puisqu’elles
sont indexées par i), et d’autre part incorrectes, car il fallait tenir compte du nombre
d’instructions MAMIAS nécessaires pour traduire une <instruction> (ou une série
l’instruction complexe elle-même. En effet, dans nos traductions, nous avons, pour simplifier
le problème, fait comme si les instructions que l’on assemblaient étaient traduites par une
seule ligne MAMIAS. Or le nombre de lignes nécessaires pour traduire une instruction
figurant dans une instruction complexe n’est pas connu au moment où l’on veut effectuer la
traduction de l’intruction complexe. Mais si l’on procède en commençant par traduire les
instructions simples, puis les instructions complexes les plus internes, et ensuite les
instructions complexes qui les englobent, etc., on sent bien qu’il doit être possible de préciser
d’exercice, on pourra chercher à définir réellement une telle méthode, pour voir quelles sont
31