汇编语言循环结构.md

原始源文件

---
ingested: true
ingestedAt: 2026-05-17
---
标题: 汇编语言 - 循环结构
链接: https://www.runoob.com/assembly/assembly-loop.html
内容: 
## 汇编语言 - 循环结构

循环是程序中最常见的控制结构之一。在汇编语言中,你可以用 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 的变体

| 指令              | 跳转条件              | 说明              |
| --------------- | ----------------- | --------------- |
| 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  

---

## 条件循环(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 时有效。完整的数字转字符串算法将在"数字处理"章节详细讲解。