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

; Chemin du fichier : loop_basic.asm
; 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

InstructionCondition de sautDescription
LOOPECX != 0Boucle standard : décrémenter ECX d'abord, puis tester
LOOPE / LOOPZECX != 0 et ZF = 1Continuer la boucle si égal
LOOPNE / LOOPNZECX != 0 et ZF = 0Continuer la boucle si différent

Exemple

; Exemple de LOOPE : trouver la première valeur non nulle dans un tableau

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

; Chemin du fichier : while_loop.asm
; 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

; Boucle DO-WHILE : s'exécute au moins une fois

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

; Chemin du fichier : nested_loop.asm
; 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

; Comparaison d'optimisation de boucle

; 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

; Chemin du fichier : sum_1_to_100.asm
; 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

La 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 ».

Autres extensions