Version sans JavaScript, en lecture seule : les ateliers, les quiz et l’exécution du code n’y sont pas. Version complète · Catalogue · Se connecter
Support de cours · chapitre 5
Sous-programmes et pile
Découper un programme en sous-programmes réutilisables, grâce à la pile qui mémorise les adresses de retour, les registres à préserver et les paramètres.
La pile
Principe
La pile est une zone de mémoire où l'on range des valeurs provisoirement : on les empile, puis on les dépile dans l'ordre inverse, comme une pile d'assiettes. Le registre A7, aussi appelé SP (stack pointer), contient l'adresse du sommet de la pile.
Sur le 68000, la pile descend : empiler fait reculer SP. On se sert des modes du chapitre 3 :
| Opération | Instruction | Effet |
|---|---|---|
empiler D0 | MOVE.W D0,-(SP) | SP recule de 2, puis D0.W est rangé à l'adresse SP |
dépiler dans D0 | MOVE.W (SP)+,D0 | le mot à l'adresse SP va dans D0, puis SP avance de 2 |
Une règle d'or
Tout ce qui est empilé doit être dépilé, dans l'ordre inverse, avant de quitter le morceau de programme qui l'a empilé. Une pile déséquilibrée, c'est un programme qui plante plus loin, loin de l'erreur.
Les sous-programmes
Appeler et revenir
Un sous-programme est un morceau de code qu'on appelle depuis plusieurs endroits, comme une fonction.
BSR ETIQ(branch to subroutine) empile l'adresse de retour (l'adresse de l'instruction qui suit leBSR, un mot long), puis saute àETIQ.JSRfait la même chose avec n'importe quel mode d'adressage.RTS(return from subroutine) dépile un mot long dansPC: l'exécution reprend juste après leBSR.
C'est pour cela que la règle d'or compte : si le sous-programme laisse quelque chose sur la pile, RTS dépile cette valeur à la place de l'adresse de retour, et le programme saute n'importe où.
Préserver les registres
Un sous-programme utilise des registres ; l'appelant aussi. Convention : un sous-programme rend intacts les registres qu'il ne sert pas à renvoyer. Il les sauve en entrant et les restaure en sortant. MOVEM (move multiple) le fait en une instruction :
SOUSPROG MOVEM.L D1-D3/A0,-(SP) ; sauver D1, D2, D3 et A0
... ; le travail
MOVEM.L (SP)+,D1-D3/A0 ; les restaurer
RTS
Passer les paramètres
Par registres. Le plus simple : l'appelant range les paramètres dans des registres convenus, et le résultat revient dans un autre. Il faut l'écrire en tête du sous-programme.
* STRLEN : longueur d'une chaîne terminée par 0
* entrée : A0 = adresse de la chaîne
* sortie : D0.L = longueur ; les autres registres sont préservés
STRLEN MOVE.L A0,-(SP) ; A0 va bouger : le sauver
MOVEQ #0,D0
BCL TST.B (A0)+
BEQ FIN
ADDQ.L #1,D0
BRA BCL
FIN MOVE.L (SP)+,A0 ; le restaurer
RTS
Appel :
LEA MESSAGE,A0
BSR STRLEN ; D0 <- longueur de MESSAGE
Par la pile. Quand les paramètres sont nombreux, ou pour écrire comme un compilateur, l'appelant les empile avant le BSR. Dans le sous-programme, ils sont sous l'adresse de retour : le dernier empilé est à 4(SP), le précédent à 6(SP) (pour des mots). Au retour, l'appelant les retire en avançant SP.
* appel : SOMME(3, 4)
MOVE.W #3,-(SP) ; premier paramètre
MOVE.W #4,-(SP) ; second paramètre
BSR SOMMEP
ADDQ.L #4,SP ; retirer les deux paramètres
... ; D0.W vaut 7
* SOMMEP : D0.W <- somme des deux mots empilés
SOMMEP MOVE.W 4(SP),D0 ; second paramètre (sous l'adresse de retour)
ADD.W 6(SP),D0 ; premier paramètre
RTS
La pile, juste après le BSR :
| Adresse | Contenu |
|---|---|
SP | adresse de retour (mot long) |
SP + 4 | 4, le second paramètre |
SP + 6 | 3, le premier paramètre |
La récursivité
Un sous-programme peut s'appeler lui-même. Chaque appel a besoin de ses propres valeurs : on les range sur la pile, qui en garde une par appel en cours.
* FACT : D1.L <- n!, pour n = D0.W, avec n <= 9
FACT TST.W D0
BNE RECUR
MOVEQ #1,D1 ; 0! = 1
RTS
RECUR MOVE.W D0,-(SP) ; sauver n : l'appel suivant va changer D0
SUBQ.W #1,D0
BSR FACT ; D1 <- (n-1)!
MOVE.W (SP)+,D0 ; retrouver n
MULU.W D0,D1 ; D1 <- n * (n-1)!
RTS
Pour $n = 3$, trois valeurs de $n$ sont empilées (3, 2, 1) avant d'atteindre $0! = 1$, puis dépilées une à une, chacune multipliant le résultat. La limite $n \le 9$ vient de MULU.W, qui ne multiplie que le mot bas de D1 : il faut que $(n-1)!$ tienne sur 16 bits, soit au plus 65535, et $8! = 40320$ tient, $9! = 362880$ non.
À essayer
La factorielle, avec une saisie : le programme demande $n$ (service 4 de TRAP #15) et affiche $n!$. Exécutez-le pas à pas et suivez A7 : il descend de 6 octets à chaque appel récursif (le mot de $n$ et l'adresse de retour), puis remonte. Essayez $n = 10$ : le résultat est faux, pour la raison donnée plus haut.
ORG $1000
START LEA INVITE,A1
MOVE.B #14,D0 ; service 14 : afficher la chaîne (A1)
TRAP #15
MOVE.B #4,D0 ; service 4 : lire un nombre dans D1.L
TRAP #15
MOVE.W D1,D0 ; n
BSR FACT ; D1 <- n!
MOVE.B #3,D0 ; service 3 : afficher D1.L
TRAP #15
SIMHALT
* FACT : D1.L <- n!, pour n = D0.W, avec n <= 9
FACT TST.W D0
BNE RECUR
MOVEQ #1,D1 ; 0! = 1
RTS
RECUR MOVE.W D0,-(SP) ; sauver n
SUBQ.W #1,D0
BSR FACT ; D1 <- (n-1)!
MOVE.W (SP)+,D0 ; retrouver n
MULU.W D0,D1 ; D1 <- n * (n-1)!
RTS
INVITE DC.B 'n = ',0
END START