Problème — Un ordinateur à programme enregistré, en Python
Exercice de TD · niveau 3 (difficile) · NSI (terminale), chapitre 15 — Histoire de l'informatique
Énoncé
Problème — Un ordinateur à programme enregistré, en Python.
On reconstitue une machine minimale conforme au rapport de 1945. Sa mémoire est une liste de entiers ; les instructions y sont rangées comme les données. Une instruction est un entier de la forme , avec le jeu d'instructions suivant, où acc désigne l'accumulateur et co le compteur ordinal :
| Code | Nom | Effet |
|---|---|---|
| 0 | STOP | arrêter la machine |
| 1 | CHARGE a | `acc` `memoire[a]` |
| 2 | RANGE a | `memoire[a]` `acc` |
| 3 | AJOUTE a | `acc` `acc + memoire[a]` |
| 4 | SOUSTRAIT a | `acc` `acc - memoire[a]` |
| 5 | SAUTE a | `co` `a` |
| 6 | SAUTE_SI_POSITIF a | si `acc > 0`, alors `co` `a` |
- Écrire la fonction
executer(memoire)qui applique le cycle « lire, décoder, exécuter » jusqu'à l'instruction STOP. - Dérouler les trois premières instructions du programme donné ci-dessous, qui calcule .
- Que contient la case à la fin ?
- Écrire trois instructions qui modifient une instruction du programme. Que montre cette possibilité, et pourquoi l'évite-t-on aujourd'hui ?
Corrigé
1. La machine. Le cycle tient en une dizaine de lignes : c'est exactement le schéma de l'architecture à programme enregistré.
def executer(memoire):
acc = 0 # accumulateur
co = 0 # compteur ordinal
while True:
instruction = memoire[co]
code = instruction // 100
adresse = instruction % 100
co = co + 1 # on avance AVANT d'executer
if code == 0:
return memoire
elif code == 1:
acc = memoire[adresse]
elif code == 2:
memoire[adresse] = acc
elif code == 3:
acc = acc + memoire[adresse]
elif code == 4:
acc = acc - memoire[adresse]
elif code == 5:
co = adresse
elif code == 6:
if acc > 0:
co = adresse
Le programme s'écrit directement en nombres, puisque c'est ainsi qu'il est rangé :
memoire = [0] * 100
memoire[0] = 121 # CHARGE 21 acc <- somme
memoire[1] = 320 # AJOUTE 20 acc <- somme + n
memoire[2] = 221 # RANGE 21 somme <- acc
memoire[3] = 120 # CHARGE 20 acc <- n
memoire[4] = 422 # SOUSTRAIT 22 acc <- n - 1
memoire[5] = 220 # RANGE 20 n <- acc
memoire[6] = 600 # SAUTE_SI_POSITIF 0 si n > 0, on recommence
memoire[7] = 0 # STOP
memoire[20] = 5 # n
memoire[21] = 0 # somme
memoire[22] = 1 # la constante 1
executer(memoire)
print(memoire[21]) # affiche 15
2. Déroulé. En co = 0, l'instruction vaut : code , adresse , donc acc memoire[21] . En co = 1, l'instruction donne acc . En co = 2, l'instruction range dans la case . Les instructions à décrémentent le compteur rangé en case , et l'instruction renvoie l'exécution en tant qu'il reste positif.
3. Résultat. La case contient successivement , , , , puis : c'est bien , la case valant alors .
4. Un programme qui se modifie lui-même. Puisque les instructions sont des nombres rangés en mémoire, rien n'interdit de les manipuler :
memoire[30] = 1
# CHARGE 0 ; AJOUTE 30 ; RANGE 0
# l'instruction 121 (CHARGE 21) devient 122 (CHARGE 22)
Cette possibilité découle directement du choix de 1945 : une seule mémoire pour les instructions et les données. C'est elle qui rend possibles les compilateurs, les interpréteurs et les systèmes d'exploitation — un programme qui en fabrique ou en lance un autre. Très employée dans les années 1950 faute de mémoire, on l'évite aujourd'hui : un programme qui se réécrit est illisible, impossible à tester, et c'est le mécanisme même de nombreuses attaques informatiques — les systèmes modernes marquent d'ailleurs les zones de code comme non modifiables. La leçon est générale : la puissance d'un mécanisme et sa dangerosité ont souvent la même origine.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.