现在位置: 首页 > 汇编语言 > 正文

汇编语言 - 循环结构

循环是程序中最常见的控制结构之一。在汇编语言中,你可以用 LOOP 指令或条件跳转来实现各种循环逻辑。


LOOP 指令

LOOP 是 x86 专门为循环设计的指令,它使用 ECX 作为计数器:

每次执行 LOOP 时,ECX 自动减 1,如果 ECX 不为 0 就跳转到目标标签。

实例

; 文件路径:loop_basic.asm
; LOOP 指令基本用法:重复 5 次输出

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

section .text
    global _start

_start:
    mov ecx, 5                  ; 循环计数器 = 5(循环 5 次)

repeat:
    ; 保存 ecx(系统调用可能修改它)
    push ecx

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

    ; 恢复 ecx
    pop ecx
    loop repeat                 ; ecx--; if ecx != 0 跳转到 repeat

    mov eax, 1
    mov ebx, 0
    int 0x80

运行结果:

$ nasm -f elf32 loop_basic.asm -o loop_basic.o
$ ld -m elf_i386 loop_basic.o -o loop_basic
$ ./loop_basic
runoob
runoob
runoob
runoob
runoob

loop 默认使用 ECX(32位)作为计数器。在 16 位模式下使用 CX,64 位模式下使用 RCX。务必在 loop 之前正确设置 ECX 的值。


LOOP 的变体

指令跳转条件说明
LOOPECX != 0标准循环,先减 ECX 再判断
LOOPE / LOOPZECX != 0 且 ZF = 1相等时继续循环
LOOPNE / LOOPNZECX != 0 且 ZF = 0不等时继续循环

实例

; LOOPE 示例:在数组中查找第一个非零值

section .data
    array db 0, 0, 0, 5, 0, 0  ; 前三个是 0,第四个是 5

section .text
    global _start

_start:
    mov ecx, 6                  ; 数组长度
    mov esi, array - 1          ; 指向数组前一个位置

find_nonzero:
    inc esi                     ; 移动到下一个元素
    cmp byte [esi], 0           ; 当前元素 == 0 ?
    loope find_nonzero          ; 如果是 0 且 ecx > 0,继续循环
    ; 循环结束:找到了第一个非零元素,或遍历结束

    ; esi 现在指向第一个非零元素的地址
    ; ecx 剩余未比较的元素个数

    mov eax, 1
    mov ebx, 0
    int 0x80

条件循环(WHILE 循环)

用条件跳转实现 while 循环:先判断条件,再执行循环体。

实例

; 文件路径:while_loop.asm
; 实现:while (x < 100) x = x * 2;

section .data
    x dd 1
    limit dd 100

section .text
    global _start

_start:
    ; while 循环
while_start:
    mov eax, [x]                ; 加载 x
    cmp eax, [limit]            ; x < 100 ?
    jge while_end               ; 如果 x >= 100,结束循环

    shl eax, 1                  ; x = x * 2
    mov [x], eax                ; 存回 x

    jmp while_start             ; 回到循环开始,重新判断

while_end:
    ; x 现在是 128(1->2->4->8->16->32->64->128,128 >= 100 停止)

    mov eax, 1
    mov ebx, [x]                ; 返回 x 作为退出码
    int 0x80

DO-WHILE 循环

先执行循环体,再判断条件:

实例

; DO-WHILE 循环:至少执行一次

section .data
    counter dd 0

section .text
    global _start

_start:
    mov dword [counter], 0

do_loop:
    ; 循环体:counter++(至少执行一次)
    inc dword [counter]

    ; 判断条件
    cmp dword [counter], 5
    jl  do_loop                 ; counter < 5 时继续
    ; counter 最终 = 5

    mov eax, 1
    mov ebx, 0
    int 0x80

嵌套循环

嵌套循环需要注意外层计数器的保存:

实例

; 文件路径:nested_loop.asm
; 嵌套循环示例:打印乘法表(3×3)

section .data
    space db ' '
    newline db 0xA
    ; 用于存放转换后的数字字符(2位 + 空格 + null)
    num_str db '  ', 0

section .text
    global _start

_start:
    mov ecx, 3                  ; 外层循环计数器(行数)

outer_loop:
    push ecx                    ; ★ 保存外层计数器!(重要)
    mov ecx, 3                  ; 内层循环计数器(列数)

inner_loop:
    push ecx                    ; 保存内层计数器

    ; 计算乘积 = 外层行号(4-当前ecx) × 内层列号
    ; 此处省略具体数字转换输出,仅演示结构
    ; 实际输出:行号×列号

    pop ecx                     ; 恢复内层计数器
    loop inner_loop             ; 内层循环

    ; 输出换行
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, 1
    int 0x80

    pop ecx                     ; ★ 恢复外层计数器
    loop outer_loop             ; 外层循环

    mov eax, 1
    mov ebx, 0
    int 0x80

嵌套循环中最常见的错误是忘记用栈保存外层计数器的值。LOOP 会修改 ECX,当你在内层循环中重新设置 ECX 时,外层的计数值就丢失了。解决方法是每次进入内层循环前 push ecx,离开后 pop ecx


循环优化技巧

一些提高循环效率的方法:

实例

; 循环优化对比

; 方式A:使用 LOOP 指令(较慢,但不明显)
    mov ecx, 1000
loop_a:
    add eax, [ebx]
    add ebx, 4
    loop loop_a

; 方式B:使用条件跳转(手工计数)(通常更快)
    mov ecx, 1000
loop_b:
    add eax, [ebx]
    add ebx, 4
    dec ecx
    jnz loop_b

; 方式C:循环展开(最快,但代码体积大)
    ; 将 1000 次循环展开为每次处理 4 个元素
    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

; 方式D:倒计数(减少比较指令)
    mov ecx, 1000
    lea ebx, [array + 1000*4 - 4]  ; 从数组末尾开始
loop_reverse:
    add eax, [ebx]
    sub ebx, 4
    dec ecx
    jnz loop_reverse

完整示例:计算 1 到 100 的和

实例

; 文件路径:sum_1_to_100.asm
; 计算 1+2+...+100 = 5050

section .data
    msg db 'Sum of 1 to 100 is: '
    msg_len equ $ - msg
    newline db 0xA

section .bss
    result_str resb 12           ; 存放转换后的数字字符串

section .text
    global _start

_start:
    ; 计算累加和
    mov ecx, 100                 ; 循环 100 次
    mov eax, 0                   ; 累加和初始为 0
    mov ebx, 1                   ; 当前要加的数

sum_loop:
    add eax, ebx                 ; eax += ebx
    inc ebx                      ; 下一个数
    loop sum_loop                ; 继续循环
    ; eax = 5050

    ; 输出提示文字
    push eax                     ; 保存结果
    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, msg_len
    int 0x80
    pop eax                      ; 恢复结果

    ; 将数字转为字符串(简单版:直接输出整数值)
    ; 这里简化处理,将 eax 的低字节转为字符
    add al, '0'                  ; 不准确,仅演示
    mov [result_str], al

    ; 输出结果
    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

数字转字符串是汇编中的一个常见问题。上面的简单方法只在数字是 0-9 时有效。完整的数字转字符串算法将在"数字处理"章节详细讲解。