汇编语言 - 循环结构
循环是程序中最常见的控制结构之一。在汇编语言中,你可以用 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
; 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 的变体
| 指令 | 跳转条件 | 说明 |
|---|---|---|
| LOOP | ECX != 0 | 标准循环,先减 ECX 再判断 |
| LOOPE / LOOPZ | ECX != 0 且 ZF = 1 | 相等时继续循环 |
| LOOPNE / LOOPNZ | ECX != 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
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
; 实现: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
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
; 嵌套循环示例:打印乘法表(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
; 方式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
; 计算 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 时有效。完整的数字转字符串算法将在"数字处理"章节详细讲解。
