Langage d'assemblage - Structures de boucle
Les boucles sont l'une des structures de contrôle les plus courantes dans les programmes. En langage assembleur, vous pouvez utiliser l'instruction LOOP ou des sauts conditionnels pour implémenter diverses logiques de boucle.
Instruction LOOP
LOOPIl s'agit d'une instruction x86 spécialement conçue pour les boucles, utilisant ECX comme compteur :
Chaque fois que LOOP est exécutée, ECX est automatiquement décrémenté de 1 ; si ECX n'est pas 0, elle saute vers l'étiquette cible.
Exemple
; Utilisation de base de l'instruction LOOP : répéter la sortie 5 fois
section .data
msg db 'example', 0xA
len equ $ - msg
section .text
global _start
_start:
mov ecx, 5 ; Compteur de boucle = 5 (boucle 5 fois)
repeat:
; Sauvegarder ecx (les appels système peuvent le modifier)
push ecx
; Afficher msg
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
; Restaurer ecx
pop ecx
loop repeat ; ecx--; si ecx != 0, sauter à repeat
mov eax, 1
mov ebx, 0
int 0x80
Sortie :
$ nasm -f elf32 loop_basic.asm -o loop_basic.o $ ld -m elf_i386 loop_basic.o -o loop_basic $ ./loop_basic example example example example example
loopPar défaut, ECX (32 bits) est utilisé comme compteur. En mode 16 bits, CX est utilisé ; en mode 64 bits, RCX est utilisé. Veillez à définir correctement ECX avant la boucle.
Variantes de LOOP
| Instruction | Condition de saut | Description |
|---|---|---|
| LOOP | ECX != 0 | Boucle standard : décrémenter ECX d'abord, puis tester |
| LOOPE / LOOPZ | ECX != 0 et ZF = 1 | Continuer la boucle si égal |
| LOOPNE / LOOPNZ | ECX != 0 et ZF = 0 | Continuer la boucle si différent |
Exemple
section .data
array db 0, 0, 0, 5, 0, 0 ; Les trois premières sont 0, la quatrième est 5
section .text
global _start
_start:
mov ecx, 6 ; Longueur du tableau
mov esi, array - 1 ; Pointer vers une position avant le tableau
find_nonzero:
inc esi ; Passer à l'élément suivant
cmp byte [esi], 0 ; L'élément courant est-il == 0 ?
loope find_nonzero ; S'il vaut 0 et que ecx > 0, continuer la boucle
; Fin de la boucle : premier élément non nul trouvé, ou parcours terminé
; esi pointe maintenant à l'adresse du premier élément non nul
; ecx contient le nombre d'éléments pas encore comparés
mov eax, 1
mov ebx, 0
int 0x80
Boucle conditionnelle (boucle WHILE)
Utilisez des sauts conditionnels pour implémenter une boucle while : testez d'abord la condition, puis exécutez le corps de la boucle.
Exemple
; Implémente : while (x < 100) x = x * 2;
section .data
x dd 1
limit dd 100
section .text
global _start
_start:
; boucle while
while_start:
mov eax, [x] ; Charger x
cmp eax, [limit] ; x < 100 ?
jge while_end ; Si x >= 100, quitter la boucle
shl eax, 1 ; x = x * 2
mov [x], eax ; Stocker x à nouveau
jmp while_start ; Revenir au début de la boucle et retester
while_end:
; x vaut maintenant 128 (1->2->4->8->16->32->64->128, s'arrête lorsque 128 >= 100)
mov eax, 1
mov ebx, [x] ; Renvoie x comme code de sortie
int 0x80
Boucle DO-WHILE
Exécutez d'abord le corps de la boucle, puis testez la condition :
Exemple
section .data
counter dd 0
section .text
global _start
_start:
mov dword [counter], 0
do_loop:
; Corps de la boucle : counter++ (s'exécute au moins une fois)
inc dword [counter]
; Test de la condition
cmp dword [counter], 5
jl do_loop ; Continuer tant que le compteur < 5
; compteur finalement = 5
mov eax, 1
mov ebx, 0
int 0x80
Boucles imbriquées
Les boucles imbriquées nécessitent une attention particulière pour sauvegarder le compteur de la boucle externe :
Exemple
; Exemple de boucle imbriquée : afficher une table de multiplication (3×3)
section .data
space db ' '
newline db 0xA
; Utilisé pour stocker les caractères numériques convertis (2 chiffres + espace + null)
num_str db ' ', 0
section .text
global _start
_start:
mov ecx, 3 ; Compteur de la boucle externe (nombre de lignes)
outer_loop:
push ecx ; ★ Sauvegardez le compteur de la boucle externe ! (Important)
mov ecx, 3 ; Compteur de la boucle interne (nombre de colonnes)
inner_loop:
push ecx ; Sauvegarder le compteur de la boucle interne
; Calculer le produit = numéro de ligne externe (4 - ecx actuel) × numéro de colonne interne
; La sortie de conversion du nombre spécifique est omise ici, seule la structure est démontrée
; Sortie réelle : numéro de ligne × numéro de colonne
pop ecx ; Restaurer le compteur de la boucle interne
loop inner_loop ; Boucle interne
; Afficher un saut de ligne
mov eax, 4
mov ebx, 1
mov ecx, newline
mov edx, 1
int 0x80
pop ecx ; ★ Restaurer le compteur de la boucle externe
loop outer_loop ; Boucle externe
mov eax, 1
mov ebx, 0
int 0x80
L'erreur la plus courante dans les boucles imbriquées est d'oublier d'utiliser la pile pour sauvegarder la valeur du compteur de la boucle externe. LOOP modifie ECX ; lorsque vous réinitialisez ECX dans la boucle interne, le compteur de la boucle externe est perdu. La solution est de la sauvegarder avant chaque entrée dans la boucle interne,
push ecxet de la restaurer après en être sorti.pop ecx。
Techniques d'optimisation des boucles
Quelques façons d'améliorer l'efficacité des boucles :
Exemple
; Méthode A : Utilisation de l'instruction LOOP (plus lente, mais pas évidente)
mov ecx, 1000
loop_a:
add eax, [ebx]
add ebx, 4
loop loop_a
; Méthode B : Utilisation d'un saut conditionnel (comptage manuel) (généralement plus rapide)
mov ecx, 1000
loop_b:
add eax, [ebx]
add ebx, 4
dec ecx
jnz loop_b
; Méthode C : Déroulage de boucle (le plus rapide, mais taille de code importante)
; Dérouler une boucle de 1000 itérations pour traiter 4 éléments par itération
mov ecx, 250 ; 1000 / 4 = 250
loop_unrolled:
add eax, [ebx]
add eax, [ebx + 4]
add eax, [ebx + 8]
add eax, [ebx + 12]
add ebx, 16
dec ecx
jnz loop_unrolled
; Méthode D : compter à rebours (réduit les instructions de comparaison)
mov ecx, 1000
lea ebx, [array + 1000*4 - 4] ; Commencer à la fin du tableau
loop_reverse:
add eax, [ebx]
sub ebx, 4
dec ecx
jnz loop_reverse
Exemple complet : calculer la somme de 1 à 100
Exemple
; Calculer 1+2+...+100 = 5050
section .data
msg db 'La somme de 1 à 100 est : '
msg_len equ $ - msg
newline db 0xA
section .bss
result_str resb 12 ; Stocker la chaîne numérique convertie
section .text
global _start
_start:
; Calculer la somme cumulée
mov ecx, 100 ; Boucle 100 fois
mov eax, 0 ; La somme cumulée initiale est 0
mov ebx, 1 ; Le nombre actuel à ajouter
sum_loop:
add eax, ebx ; eax += ebx
inc ebx ; Nombre suivant
loop sum_loop ; Continuer la boucle
; eax = 5050
; Afficher le texte d'invite
push eax ; Sauvegarder le résultat
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, msg_len
int 0x80
pop eax ; Restaurer le résultat
; Convertir le nombre en chaîne (version simple : afficher directement la valeur entière)
; Simplifié ici : convertir l'octet faible de eax en un caractère
add al, '0' ; Pas exact, uniquement pour la démonstration
mov [result_str], al
; Sortie du résultat
mov eax, 4
mov ebx, 1
mov ecx, result_str
mov edx, 12
int 0x80
mov eax, 4
mov ebx, 1
mov ecx, newline
mov edx, 1
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Autres extensionsLa conversion de nombres en chaînes est un problème courant en assembleur. La méthode simple ci-dessus ne fonctionne que lorsque le nombre est compris entre 0 et 9. L'algorithme complet de conversion de nombre en chaîne sera expliqué en détail dans le chapitre « Traitement des nombres ».