II. SAUTS INCONDITIONNELS, PROCEDURES ET MACROS
1. Les sauts inconditionnels
Ce paragraphe répond à la question : « Mais
comment fait-on un GOTO en assembleur ? ».
L’instruction équivalente est JMP qui signifie « jump ».
La syntaxe est
JMP MonLabel
La référence à l’étiquette MonLabel
sera remplacée lors de la compilation par la distance
(signée) en octets qui sépare l’instruction qui
suit immédiatement le JMP de l’instruction adressée
par MonLabel. En pratique, le JMP s’utilise exactement
comme un GOTO en BASIC.
Cette instruction permet de faire un saut
inconditionnel : le programme fera ce saut quoiqu’il
arrive.
Exemple :
Le mot “short” ajouté après “jmp”
indique au compilateur que le label “COUCOU” se
trouve à une distance (signée) qui peut être
stockée sur un seul octet. Le compilateur ne le sait pas
encore lorsqu’il essaie de compiler l’instruction de saut
car le label se trouve en deçà de cette instruction.
C’est pourquoi il prévoit deux octets pour écrire
le saut, au cas où l’adresse d’arrivée soit
éloignée de plus de 128 octets. Lorsqu’il
effectue une deuxième « passe », s’il
s’aperçoit qu’un seul octet aurait suffi il
remplit alors l’octet inutile avec l’instruction NOP
(« No Operation ») qui ne fait rien du
tout. Le programme marchera parfaitement (encore heureux !),
mais cela gaspille un octet. Le programmeur a la possibilité
d’aider le compilateur en ajoutant le mot “short”,
ainsi un seul octet est prévu pour la distance de saut. Si le
label est placé plus haut que l'instruction de saut, il est
inutile de l'écrire.
Remarque :
Ce mot peut également être utilisé avec les
instructions de saut conditionnel que nous étudierons plus
loin.
2. Les procédures
a) appels de procédures
Ceux qui ont déjà programmé en BASIC connaissent les
instructions GOSUB et RETURN. Leurs équivalents en assembleur
sont CALL et RET.
Syntaxe
CALL MonLabel
Action :
empile l’offset de l’instruction suivante puis fait un
simple saut à l’adresse représentée par
MonLabel.
Syntaxe :
RET
Action :
dépile l’adresse de retour et la met dans IP. Le
programme continue donc à l’adresse qui suit le CALL.
Remarque :
Rappelez-vous qu’il est possible de terminer un programme COM
avec l’instruction RET. Puisque le DOS empile un zéro de
deux octets au chargement du programme, IP prendra la valeur 0000
lorsque le processeur exécutera cette instruction : il
pointera donc vers le début du PSP. Or le PSP commence
toujours par les deux octets suivants : CDh 20h (ce qui s’écrit
INT 20h en assembleur). L’interruption 20h, qui permet de
terminer un programme COM, sera donc appelée et le contrôle
sera rendu au DOS.
Voici un exemple d’utilisation des procédures aussi simple que
possible : ce programme COM appelle 12 fois une procédure qui
écrit un message à l’écran et rend la main
au DOS. Il n’est d’aucune utilité et n’est
pas optimisé du tout.
Remarque :
Les codes ASCII 10 et 13 représentent respectivement la fin de
ligne et le retour chariot. Grâce à eux, on revient à
la ligne chaque fois qu’on a écrit le message.
Ce programme fonctionne parfaitement. Toutefois, les conventions
d’écriture veulent que les procédures soient
écrites comme suit :
Le programme compilé est toujours le même, mais le code est
plus lisible puisqu'on distingue les procédures des simples
labels. Le mot “near” signifie que l’adresse de
cette procédure est réduite à un offset.
Il n’y a pas d’adresse de segment. La procédure ne
pourra donc être appelée que de l’intérieur
même du segment.
Le mot qui a le sens contraire de “near” est “far”.
Les « call far » sont assez peu
utilisés.
b) le passage des paramètres
Il existe deux moyens de passer des paramètres à une
procédure : les registres et la pile.
Il consiste à transmettre les paramètres dans des
registres convenus à l’avance, par exemple AX et BX. Les
deux gros avantages de cette méthode sont sa simplicité
et la vitesse d’exécution. L’inconvénient
majeur est le nombre limité de registres, d’autant plus
qu’au moment de l’appel, certains d’entre eux ne
seront peut-être pas disponibles.
C’est la méthode qu’utilisent les langages de haut niveau
tels que le C ou le PASCAL. Avant l’appel, on empile les
paramètres un à un. La procédure appelée
se chargera de les lire.
Pour cela on utilise le registre BP de la manière suivante :
on transfère la valeur de SP dans BP à l’aide de
l’instruction “MOV BP, SP”. L’adresse de
retour (qui sera dépilée quand le processeur
rencontrera l'instruction “RET”) se trouve à
l’adresse SS:[BP], le dernier paramètre est adressé
par SS:[BP + 2], l’avant-dernier par SS:[BP + 4], etc…
On peut ainsi lire n’importe quel paramètre sans le
dépiler.
Attention cependant ! A la fin de la procédure, il faudra tout de
même rendre à SP la valeur qu’il avait avant
l’empilage des paramètres. Pour cela, on fait suivre
l’instruction RET du nombre de paramètres, multiplié
par 2 (car chaque paramètre comporte deux octets).
Exemple :
Soyez toujours très vigilant avec les appels de procédures :
pensez que l’adresse de retour sera dépilée
lorsque le programme rencontrera l’instruction RET. Il serait
donc suicidaire de laisser une donnée empilée avant
d’appeler cette instruction.
3. Les macros
Etant donné que certaines instructions se répètent
constamment dans un programme, l’écriture de
macrofonctions (ou macros)
est un moyen pratique de rendre votre code source plus lisible.
Il est possible de choisir pour certaines suites d’instructions un
nom qui les représente. Lorsque le compilateur (en fait, le
préprocesseur) rencontrera ce nom dans votre code source, il
le remplacera par les lignes de code qu’il désigne. Ces
lignes forment une « macro ».
Les macros, à la différence des procédures, n’ont
aucune signification pour la machine. Seul le compilateur comprend
leur signification. Elles ne sont qu’un artifice mis à
la disposition du programmeur pour clarifier son programme. Lorsque
le compilateur rencontre le nom d’une macro dans votre code, il
le remplace par le code de la macro. Tout se
passe exactement comme si vous aviez tapé vous-même ce
code à la place du nom de la macro.
Ainsi, si vous appelez quinze fois une macro dans votre programme, le
compilateur écrira quinze fois le code de cette macro. C’est
toute la différence avec les procédures qui ne sont
écrites qu’une seule fois mais peuvent être
appelées aussi souvent qu’on veut à l’aide
d’un CALL.
Voici comment écrire une macro : l’exemple suivant sert à
écrire un message à l’écran.
Le code précédent peut être écrit n’importe
où dans votre programme, à condition qu’il se
trouve avant tout appel de cette macro. Afin d’éviter
les ennuis, il est fortement conseillé de réunir vos
macros au début du code, avant toute autre ligne. De toute
façon, il ne sera pas compilé à l’endroit
où vous l’avez écrit mais aux endroits où
se trouvent les appels de macros.
Le mot “text?” est un « paramètre ».
Le point d’interrogation n’est pas requis ; nous
l’avons mis pour indiquer qu’il s’agit d’un
paramètre et non d’une variable. Une macro peut utiliser
plusieurs paramètres séparés par des virgules.
Pour appeler cette macro, il vous suffit d’écrire la ligne
ecrit_texte “Coucou ! Ceci est un essai !”
Le compilateur se chargera alors de la remplacer par les instructions
comprises entre la première et la dernière ligne de cet
exemple, en prenant le soin de remplacer le mot “text?”
par le message fourni en paramètre.
En assembleur, chaque label doit avoir un nom unique. Or, si une macro
est appelée plusieurs fois, les mêmes noms seront
utilisés. Il faut donc la plupart du temps inclure la
directive LOCAL qui forcera le compilateur à changer le nom
des labels à chaque appel de la macro. Attention : cette
directive doit suivre immédiatement la déclaration de la macro.
Supposons à présent que l’on veuille écrire à
l’écran le message « Je suis bien content »
et revenir à la ligne à l’aide de notre macro ecrit_texte.
La syntaxe suivante :
ecrit_texte “Coucou ! Ceci est un essai !”, 10, 13
est incorrecte, car le compilateur croirait que l’on a écrit
trois paramètres ! Il faut alors entourer notre unique
paramètre par les signes ‘<’ et ‘>’
:
ecrit_texte <”Coucou ! Ceci est un essai !”, 10, 13>
4. La directive EQU
La directive EQU a un rôle voisin de celui des macros. Elle permet
de remplacer un simple mot par d’autres plus complexes. Son
intérêt est qu’elle peut être invoquée
en plein milieu d’une ligne.
Quelques exemples :
Longueur EQU (fin – debut)
Message EQU “Bonjour messieurs ! Comment allez-vous ?”, ‘$’
Version EQU 2
Quitter EQU ret
Quitter2 EQU int 20h
Mettre_dans_AH EQU mov ah,
Interruption_21h EQU int 21h
Un programme qui contient de telles directives peut se terminer ainsi :
Mettre_dans_AH 4Ch
Interruption_21h
Ou ainsi (si c’est un COM) :
Quitter2
5. L’inclusion de fichiers
Il est possible d’accéder à des procédures,
des macros ou des définitions EQU qui se trouvent dans
d’autres fichiers. Cela permet de se constituer des librairies
de macros ou de procédures que l’on peut réutiliser
d’un programme à l’autre.
Pour inclure le fichier TOTO.LIB, écrivez au début de votre
code source :
La condition “if1” indique au
compilateur que l’inclusion ne doit s’effectuer que lors
de la première passe.
Le fichier TOTO.LIB est un fichier
texte tout à fait banal qui contient des lignes de code en
assembleur.
Sommaire
Suite
|