Assemblersprache - Schleifenstrukturen

Schleifen gehören zu den häufigsten Kontrollstrukturen in Programmen. In Assemblersprache können Sie den LOOP-Befehl oder bedingte Sprünge verwenden, um verschiedene Schleifenlogiken zu implementieren.


LOOP-Befehl

LOOPEs handelt sich um einen speziell für Schleifen entwickelten x86-Befehl, der ECX als Zähler verwendet:

Jedes Mal, wenn LOOP ausgeführt wird, wird ECX automatisch um 1 dekrementiert; wenn ECX nicht 0 ist, springt es zur Zielmarke.

Beispiel

; Dateipfad: loop_basic.asm
; Grundlegende Verwendung des LOOP-Befehls: Ausgabe 5 Mal wiederholen

section .data
    msg db 'example', 0xA
    len equ $ - msg

section .text
    global _start

_start:
    mov ecx, 5                  ; Schleifenzähler = 5 (5-mal Schleife)

repeat:
    ; ecx speichern (Systemaufrufe könnten es modifizieren)
    push ecx

    ; msg ausgeben
    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, len
    int 0x80

    ; ecx wiederherstellen
    pop ecx
    loop repeat                 ; ecx--; wenn ecx != 0, springe zu repeat

    mov eax, 1
    mov ebx, 0
    int 0x80

Ausgabe:

$ 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

loopStandardmäßig wird ECX (32-Bit) als Zähler verwendet. Im 16-Bit-Modus wird CX verwendet; im 64-Bit-Modus wird RCX verwendet. Stellen Sie sicher, dass ECX vor der Schleife korrekt gesetzt wird.


Varianten von LOOP

AnweisungSprungbedingungBeschreibung
LOOPECX != 0Standardschleife: ECX zuerst dekrementieren, dann testen
LOOPE / LOOPZECX != 0 und ZF = 1Schleife fortsetzen, wenn gleich
LOOPNE / LOOPNZECX != 0 und ZF = 0Schleife fortsetzen, wenn ungleich

Beispiel

; LOOPE-Beispiel: Den ersten Nicht-Null-Wert in einem Array finden

section .data
    array db 0, 0, 0, 5, 0, 0  ; Die ersten drei sind 0, der vierte ist 5

section .text
    global _start

_start:
    mov ecx, 6                  ; Array-Länge
    mov esi, array - 1          ; Auf eine Position vor dem Array zeigen

find_nonzero:
    inc esi                     ; Zum nächsten Element bewegen
    cmp byte [esi], 0           ; Ist das aktuelle Element == 0?
    loope find_nonzero          ; Wenn es 0 ist und ecx > 0, Schleife fortsetzen
    ; Schleife endet: erstes Nicht-Null-Element gefunden oder Durchlauf beendet

    ; esi zeigt nun auf die Adresse des ersten Nicht-Null-Elements
    ; ecx enthält die Anzahl der noch nicht verglichenen Elemente

    mov eax, 1
    mov ebx, 0
    int 0x80

Bedingte Schleife (WHILE-Schleife)

Verwenden Sie bedingte Sprünge, um eine while-Schleife zu implementieren: Testen Sie zuerst die Bedingung und führen Sie dann den Schleifenkörper aus.

Beispiel

; Dateipfad: while_loop.asm
; Implementiert: while (x < 100) x = x * 2;

section .data
    x dd 1
    limit dd 100

section .text
    global _start

_start:
    ; while-Schleife
while_start:
    mov eax, [x]                ; x laden
    cmp eax, [limit]            ; x < 100 ?
    jge while_end               ; Wenn x >= 100, Schleife verlassen

    shl eax, 1                  ; x = x * 2
    mov [x], eax                ; x zurückspeichern

    jmp while_start             ; Zum Schleifenanfang zurückkehren und erneut testen

while_end:
    ; x ist jetzt 128 (1->2->4->8->16->32->64->128, stoppt wenn 128 >= 100)

    mov eax, 1
    mov ebx, [x]                ; x als Exit-Code zurückgeben
    int 0x80

DO-WHILE-Schleife

Führen Sie zuerst den Schleifenkörper aus und testen Sie dann die Bedingung:

Beispiel

; DO-WHILE-Schleife: wird mindestens einmal ausgeführt

section .data
    counter dd 0

section .text
    global _start

_start:
    mov dword [counter], 0

do_loop:
    ; Schleifenkörper: counter++ (wird mindestens einmal ausgeführt)
    inc dword [counter]

    ; Bedingung testen
    cmp dword [counter], 5
    jl  do_loop                 ; Fortfahren, solange counter < 5
    ; counter ist schließlich = 5

    mov eax, 1
    mov ebx, 0
    int 0x80

Verschachtelte Schleifen

Verschachtelte Schleifen erfordern Aufmerksamkeit beim Speichern des äußeren Schleifenzählers:

Beispiel

; Dateipfad: nested_loop.asm
; Beispiel für eine verschachtelte Schleife: Multiplication Table drucken (3×3)

section .data
    space db ' '
    newline db 0xA
    ; Wird verwendet, um die konvertierten numerischen Zeichen zu speichern (2 Ziffern + Leerzeichen + Null)
    num_str db '  ', 0

section .text
    global _start

_start:
    mov ecx, 3                  ; Äußerer Schleifenzähler (Zeilenanzahl)

outer_loop:
    push ecx                    ; ★ Äußeren Schleifenzähler speichern! (Wichtig)
    mov ecx, 3                  ; Innerer Schleifenzähler (Spaltenanzahl)

inner_loop:
    push ecx                    ; Inneren Schleifenzähler speichern

    ; Berechnung Produkt = äußere Zeilennummer (4 - aktuelles ecx) × innere Spaltennummer
    ; Spezifische Zahlenkonvertierungsausgabe hier ausgelassen, nur Struktur wird demonstriert
    ; Tatsächliche Ausgabe: Zeilennummer × Spaltennummer

    pop ecx                     ; Wiederherstellen des inneren Schleifenzählers
    loop inner_loop             ; Innere Schleife

    ; Output newline
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, 1
    int 0x80

    pop ecx                     ; ★ Wiederherstellen des äußeren Schleifenzählers
    loop outer_loop             ; Äußere Schleife

    mov eax, 1
    mov ebx, 0
    int 0x80

Der häufigste Fehler bei geschachtelten Schleifen besteht darin, zu vergessen, den Stapel zu verwenden, um den Wert des äußeren Schleifenzählers zu speichern. LOOP modifiziert ECX; wenn Sie ECX in der inneren Schleife zurücksetzen, geht der Zählwert der äußeren Schleife verloren. Die Lösung besteht darin, ihn vor jedem Eintritt in die innere Schleife zu speichern,push ecxund ihn nach dem Verlassen wiederherzustellen.pop ecx。


Schleifenoptimierungstechniken

Einige Möglichkeiten zur Verbesserung der Schleifeneffizienz:

Beispiel

; Vergleich der Schleifenoptimierung

; Methode A: Verwendung der LOOP-Anweisung (langsamer, aber nicht offensichtlich)
    mov ecx, 1000
loop_a:
    add eax, [ebx]
    add ebx, 4
    loop loop_a

; Methode B: Verwendung eines bedingten Sprungs (manuelles Zählen) (normalerweise schneller)
    mov ecx, 1000
loop_b:
    add eax, [ebx]
    add ebx, 4
    dec ecx
    jnz loop_b

; Methode C: Schleifenabrollung (am schnellsten, aber große Codegröße)
    ; Abrollen einer Schleife mit 1000 Iterationen, um 4 Elemente pro Iteration zu verarbeiten
    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

; Methode D: Herabzählen (reduziert die Anzahl der Vergleichsanweisungen)
    mov ecx, 1000
    lea ebx, [array + 1000*4 - 4]  ; Am Ende des Arrays beginnen
loop_reverse:
    add eax, [ebx]
    sub ebx, 4
    dec ecx
    jnz loop_reverse

Vollständiges Beispiel: Berechnen der Summe von 1 bis 100

Beispiel

; Dateipfad: sum_1_to_100.asm
; Berechne 1+2+...+100 = 5050

section .data
    msg db 'Summe von 1 bis 100 ist: '
    msg_len equ $ - msg
    newline db 0xA

section .bss
    result_str resb 12           ; Speichern der konvertierten numerischen Zeichenkette

section .text
    global _start

_start:
    ; Berechnen der kumulativen Summe
    mov ecx, 100                 ; 100 mal Schleife
    mov eax, 0                   ; Anfängliche kumulative Summe ist 0
    mov ebx, 1                   ; Die aktuelle zu addierende Zahl

sum_loop:
    add eax, ebx                 ; eax += ebx
    inc ebx                      ; Nächste Zahl
    loop sum_loop                ; Schleife fortsetzen
    ; eax = 5050

    ; Ausgeben des Aufforderungstextes
    push eax                     ; Ergebnis speichern
    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, msg_len
    int 0x80
    pop eax                      ; Ergebnis wiederherstellen

    ; Zahl in Zeichenkette konvertieren (einfache Version: direkter Integer-Wert-Ausgang)
    ; Hier vereinfacht: Konvertieren des niedrigen Bytes von eax in ein Zeichen
    add al, '0'                  ; Nicht genau, nur zur Demonstration
    mov [result_str], al

    ; Ergebnis ausgeben
    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

Das Konvertieren von Zahlen in Zeichenketten ist ein häufiges Problem in der Assemblersprache. Die obige einfache Methode funktioniert nur, wenn die Zahl zwischen 0 und 9 liegt. Der vollständige Algorithmus zur Zahlen-zu-Zeichenkette-Konvertierung wird im Kapitel „Zahlenverarbeitung“ ausführlich erklärt.

Weitere Erweiterungen