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
; 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
| Anweisung | Sprungbedingung | Beschreibung |
|---|---|---|
| LOOP | ECX != 0 | Standardschleife: ECX zuerst dekrementieren, dann testen |
| LOOPE / LOOPZ | ECX != 0 und ZF = 1 | Schleife fortsetzen, wenn gleich |
| LOOPNE / LOOPNZ | ECX != 0 und ZF = 0 | Schleife fortsetzen, wenn ungleich |
Beispiel
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
; 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
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
; 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
; 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
; 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
Weitere ErweiterungenDas 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.