第2讲:除法、分支与循环输出
本周核心:掌握 div 的寄存器约定,学会把 C 语言的 if/if-else/&&/|| 翻译成汇编,并能用循环输出矩形星号和金字塔。
一、除法指令 div
1.1 汇编语言中除法的三种用法
老师 1.asm 中的完整说明:
asm
comment @
汇编语言中的除法一共有3种用法:
(1) 16位/8位: ax/任意的8位寄存器或变量=al..ah
mov ax, 123h
mov bl, 10h
div bl ; ax / bl = al..ah, al=12h, ah=03h
(2) 32位/16位: (dx、ax)/任意的16位寄存器或变量=ax..dx
mov dx, 12h
mov ax, 3456h
mov bx, 1000h
div bx ; (dx、ax)/bx = 123456h/1000h = ax..dx,
; ax=0123h, dx=0456h
(3) 64位/32位: (edx、eax)/任意的32位寄存器或变量=eax..edx
mov edx, 0
mov eax, 123456h
mov ebx, 1000h
div ebx ; (edx、eax)/ebx=0000000000123456h/1000h
; eax=123h, edx=456h
;ebx:extended bx, 是一个32位的寄存器
@总结寄存器约定:
| 形式 | 被除数 | 除数 | 商 | 余数 |
|---|---|---|---|---|
| 16位 ÷ 8位 | AX | 8位寄存器/变量 | AL | AH |
| 32位 ÷ 16位 | DX:AX | 16位寄存器/变量 | AX | DX |
| 64位 ÷ 32位 (.386) | EDX:EAX | 32位寄存器/变量 | EAX | EDX |
高频错误:循环除法前忘记清 DX/EDX,导致被除数高位残留垃圾值。
1.2 实战:计算一个数是几位数(8 位除数版本)
思路:反复除以 10,商不为 0 就继续,每除一次位数 +1。这是老师 1.asm 中的例子——用 8 位除法计算 123 的位数:
asm
; 计算123是几位数
code segment
assume cs:code
main:
mov ax, 123
mov bl, 10
mov cx, 0
again:
div bl ; ax / bl = 123 / 10, al=12, ah=3
add cx, 1
cmp al, 0
je done
mov ah, 0 ; 清空 ah(上次除法的余数),否则下次 div 会出错
jmp again
done:
code ends
end main注意:8 位除法
div bl的被除数是 AX,商存 AL、余数存 AH。循环中必须mov ah, 0清除上次余数,否则下一轮的被除数会包含垃圾值。
1.3 16 位除数版本(老师 2.asm)
asm
; 计算12345是几位数
code segment
assume cs:code
main:
mov ax, 12345
mov bx, 10
mov cx, 0
again:
mov dx, 0 ; ★ 每次除法前必须清 dx
div bx ; (dx、ax) / bx = 12345 / 10, ax=1234, dx=5
add cx, 1
cmp ax, 0
jne again
;je done
;jmp again
done:
code ends
end main1.4 32 位除数版本(老师 3.asm)
asm
; 计算12345是几位数
.386
code segment use16
assume cs:code
main:
mov eax, 12345
mov ebx, 10
mov cx, 0
again:
mov edx, 0 ; ★ 每次除法前必须清 edx
div ebx ; (edx、eax) / ebx = 12345 / 10, eax=1234, edx=5
add cx, 1
cmp eax, 0
jne again
;je done
;jmp again
done:
code ends
end main二、条件分支翻译
2.1 基本套路
cmp a, b ← 比较
jxx label ← 根据比较结果决定跳不跳2.2 逻辑与(&&)的翻译(老师 4.asm + 4.c)
C 语言中,&& 的原始写法是嵌套 if:
c
#include <stdio.h>
main()
{
int a=2, b=-3, c;
if(a > 0)
{
if(b > 0)
c = 1;
else
c = 0;
}
else
c = 0;
/*
if(a > 0 && b > 0)
c = 1;
else
c = 0;
*/
}翻译成汇编时,「&&」意味着前一个条件不满足就直接判 false,不再检查后面(短路):
asm
code segment
assume cs:code
main:
mov ax, 2 ; a = 2
mov bx, -3 ; b = -3
cmp ax, 0
jg ax_is_positive ; a > 0 ? 是→继续检查 b
jmp set_cx_0 ; a ≤ 0 → 直接 c=0(短路!)
ax_is_positive:
cmp bx, 0
jg bx_is_positive ; b > 0 ? 是→c=1
set_cx_0:
mov cx, 0
jmp done
bx_is_positive:
mov cx, 1
done:
code ends
end main流程理解:
- 先判断
a > 0,不满足则直接跳到set_cx_0 - 满足才继续判断
b > 0 - 两个都满足才执行
bx_is_positive
2.3 逻辑或(||)的翻译(老师 5.asm + 5.c)
C 语言:
c
main()
{
int a=2, b=-3, c;
if(a > 0 || b > 0)
c = 1;
else
c = -1;
}「||」意味着前一个条件满足就直接判 true,不再检查后面:
asm
code segment
assume cs:code
main:
mov ax, 2 ; a = 2
mov bx, -3 ; b = -3
cmp ax, 0
jg set_cx_1 ; a > 0 → 直接 c=1(短路!)
cmp bx, 0
jg set_cx_1 ; b > 0 → c=1
mov cx, -1 ; 两个都不满足 → c = -1
jmp done
set_cx_1:
mov cx, 1
done:
code ends
end main总结:&& 用「不满足就跳走」,|| 用「满足就跳走」。这也叫短路求值。
三、字符输出与程序退出
汇编没有 printf,一切 I/O 通过 DOS 中断 int 21h 实现。
3.1 输出单个字符
asm
mov ah, 2 ; ah=2 用来指定子函数的编号
mov dl, '*' ; dl = 要输出的字符(ASCII 码)
int 21h ; 调用 0x21 号函数集3.2 输出回车换行
asm
mov ah, 2
mov dl, 0Dh ; 或 mov dl, 13 —— 回车符 '\r',光标回到行首
int 21h
mov ah, 2
mov dl, 0Ah ; 或 mov dl, 10 —— 换行符 '\n',光标下移一行
int 21hWindows/DOS 的换行 = 回车(0Dh) + 换行(0Ah),两者缺一不可。
3.3 程序正常退出
asm
mov ah, 4Ch
mov al, 0
int 21h ; 相当于 C 语言中的函数调用 exit(0)四、循环输出实战
4.1 理解 ZF 标志位与 jnz / je
CPU 内部有一个 FL 寄存器(标志寄存器),其中有一位叫 ZF(Zero Flag):
- 当 add、sub、cmp 等指令的运算结果 = 0 时,ZF 会被置 1,表示「zero is true」
- 当运算结果 ≠ 0 时,ZF 会被清零,表示「zero is not true」
jnz是当 ZF=0 时才跳转(jump if not zero)je是当 ZF=1 时才跳转(jump if equal / zero)
asm
mov cx, 5
sub cx, 1 ; cx=4, ZF=0
jnz again ; ZF=0 所以会发生跳转4.2 输出 5×3 的星号矩形(老师 6.asm)
用双层循环:外层控制行(bx),内层控制每行星号数(cx),jnz 控制循环。
asm
;用循环输出5*3个星号
code segment
assume cs:code
main:
mov bx, 3 ; bx=行数
next_row:
mov cx, 5 ; cx=每个星号的个数
next_star:
mov ah, 2 ; ah=2用来指定子函数的编号
mov dl, '*'
int 21h ; 调用0x21号函数集
sub cx, 1
jnz next_star ; jump if not zero
;cpu内部有一个FL寄存器,该寄存器的其中一位叫ZF
;当add、sub、cmp等指令使运算结果=0时,ZF会被置1,
;表示zero is true,反之ZF会被清零,表示zero is not true
;jnz是当ZF=0时才跳转
;je是当ZF=1时才跳转
mov ah, 2
mov dl, 0Dh ; 或mov dl, 13; 其中0Dh是回车符,即'\r'
int 21h
mov ah, 2
mov dl, 0Ah ; 或mov dl, 10; 其中0Ah是换行符,即'\n'
int 21h
sub bx, 1
jnz next_row
mov ah, 4Ch
mov al, 0
int 21h ; 相当于C语言中的函数调用exit(0)
code ends
end main运行结果:
*****
*****
*****4.3 输出三层金字塔(老师 7.asm)
*
***
*****规律分析(设行号为 bx,从 1 开始,总层数 = 3):
- 第 bx 行的空格数 = 3 - bx
- 第 bx 行的星号数 = bx × 2 - 1
asm
comment #
用循环输出一个3层金字塔:
*
***
*****
#
code segment
assume cs:code
main:
mov bx, 1 ; 行号
next_row:
mov cx, bx
add cx, cx
sub cx, 1 ; cx=星号的个数
mov bp, 3 ; 3是金字塔的层数
sub bp, bx ; bp=空格的个数
output_1_space:
cmp bp, 0
je output_1_star
mov ah, 2
mov dl, ' '
int 21h ; 输出一个空格
sub bp, 1
jmp output_1_space
output_1_star:
mov ah, 2
mov dl, '*'
int 21h ; 输出一个星号
sub cx, 1
jnz output_1_star ; jnz:jump if not zero
mov ah, 2
mov dl, 0Dh
int 21h ; 输出一个回车符'\r'
mov ah, 2
mov dl, 0Ah
int 21h ; 输出一个换行符'\n'
add bx, 1
cmp bx, 3
jle next_row
mov ah, 4Ch
mov al, 0
int 21h ; 相当于C语言函数调用exit(0), 终止程序的运行
code ends
end main五、汇编多行注释
asm
comment @
这是多行注释
可以写很多行
@或:
asm
comment #
这也是多行注释
#六、本周易错点
- div 前忘记清 DX/EDX,或者 8 位除法忘记
mov ah, 0 - && 和 || 翻译时短路逻辑写反(该跳时不跳)
- 字符输出忘记设置 AH=2
- 换行只写 0Ah 漏写 0Dh
- 循环条件取反写错(如把
jle写成jg)