第3讲:算术运算、位运算与进制转换
本周核心:掌握有符号/无符号运算的区别,熟练使用位运算与移位,能写出 32 位整数转十六进制/二进制的输出程序。
一、算术运算
老师 1.asm 知识点总讲义全部内容如下。
1.1 加减法
add ax, bx ; ax = ax + bx
sub ax, bx ; ax = ax - bx
inc ax ; ax++
dec ax ; ax--
neg ax ; ax = -ax(取相反数)
cmp ax, bx ; 计算 ax - bx,只影响标志位,不保存结果1.2 乘法:imul(有符号)与 mul(无符号)
imul ax, bx — 双操作数有符号乘法:
; --- 两个有符号数相乘,不溢出 ---
mov ax, 2
mov bx, 3
imul ax, bx ; ax = ax * bx = 2*3 = 0006h
; OF=0, CF=0当结果超出寄存器宽度时,会被截断:
; --- 溢出示例 ---
mov ax, 1234h
mov bx, 100h
imul ax, bx ; ax = 123400h 无法保存
; ax = 3400h(高位被截断)
; OF=1, CF=1mul bx — 无符号乘法(单操作数形式):
16 位 × 16 位 → 32 位结果存在 dx:ax 中:
mov ax, 2
mov bx, 3
mul bx ; ax * bx = dx、ax
; dx=0000h, ax=0006himul 的三种形式(老师 imul.asm 的完整补充):
.386
code segment use16
assume cs:code
main:
; --- 形式1:imul ax, bx(双操作数)---
mov ax, 1234h
mov bx, 100h
imul ax, bx
; --- 形式2:imul bx(单操作数,结果在 dx:ax)---
mov ax, 1234h
mov bx, 100h
imul bx
; --- 形式3:有符号单操作数 ---
mov ax, -2
mov bx, 2
imul bx
code ends
end main单操作数形式
imul bx等同于mul bx的语义(结果在 dx:ax),但是按有符号数解释。
1.3 除法:idiv(有符号除法)
老师 idiv.asm 的完整例子:
.386
code segment use16
assume cs:code
main:
mov dx, -1
mov ax, -5 ; dx、ax = 0FFFFFFFBh = -5
mov bx, 2
idiv bx ; dx、ax / bx
; ax=0FFFEh=-2, dx=0FFFFh=-1
code ends
end main有符号除法
idiv bx的结果:商在 AX(-2),余数在 DX(-1)。做有符号除法前,用cwd(ax → dx:ax)扩展符号位。
二、关系运算:有符号 vs 无符号比较
先用 cmp 比较,再跟随 j 开头的条件跳转指令实现分支。
2.1 跳转指令速查
(1) 有符号数(signed)比较相关的跳转:
jg jl jge jle je jne(2) 无符号数(unsigned)比较相关的跳转:
ja jb jae jbe je jne2.2 同一比特模式,不同语义得出相反结论
mov ax, 0FFFFh ; 有符号:FFFFh = -1;无符号:FFFFh = 65535
mov bx, 1
cmp ax, bx
jl ax_is_less_than_bx ; -1 < 1,会发生跳转!mov ax, 0FFFFh ; 65535
mov bx, 1
cmp ax, bx
jb ax_is_below_bx ; 65535 > 1,不会发生跳转!2.3 16 位计数器减到 0 以下的陷阱
mov cx, 0
sub cx, 1 ; cx = 0FFFFh = 65535
cmp cx, 0
jb done ; 无符号比较:65535 > 0,不会跳转
; 如果用 jl(有符号),FFFFh = -1 < 0,会错误跳出规则:有符号数用 jg/jl/jge/jle,无符号数用 ja/jb/jae/jbe。混用会导致逻辑错误。
三、位运算
3.1 基本位运算对应关系
| C 语言 | 汇编指令 | 中文 |
|---|---|---|
& | and | 与 |
| | or | 或 |
^ | xor | 异或 |
~ | not | 非 |
C 语言的 _rotl() = 循环左移库函数,_rotr() = 循环右移库函数。汇编有对应的 rol、ror 指令。
下面逐个演示老师 1.asm 中的完整例子。
3.2 按位与(and)
mov ah, 10110110B ; 或写成 mov ah, 0B6h(B 是二进制常数的后缀)
mov bh, 01011010B
and ah, bh ; ah = ah & bh = 00010010B = 12h 10110110
01011010 and)
-----------
000100103.3 按位或(or)
mov ah, 10110110B ; 或写成 mov ah, 0B6h
mov bh, 01011010B
or ah, bh ; ah = ah | bh = 11111110B = 0FEh 10110110
01011010 or)
-----------
111111103.4 按位异或(xor)
mov ah, 10110110B ; 或写成 mov ah, 0B6h
mov bh, 01011010B
xor ah, bh ; ah = ah ^ bh = 11101100B = 0ECh 10110110
01011010 xor)
-----------
111011003.5 按位取反(not)
mov ah, 10110110B ; 或写成 mov ah, 0B6h
not ah ; ah = 01001001B = 49h 10110110 not)
-----------
010010013.6 清除寄存器的最快方法
xor ax, ax ; ax = 0,比 mov ax, 0 更快
xor eax, eax ; eax = 0四、移位指令
4.1 逻辑左移 shl
mov ah, 3
shl ah, 1 ; 左移1位, shl: shift left 逻辑左移 0000 0011 (3, 移位前)
0000 0110 (6, 移位后)
移出去的原最高位自动进入 CF,本例中 CF=0
左移1位相当于乘以2
shl和sal是同一指令的别名。
4.2 逻辑右移 shr
mov ah, 3
shr ah, 1 ; 右移1位, shr: shift right 逻辑右移
; 右移1位相当于除以2 0000 0011 (3, 移位前)
0000 0001 (1, 移位后)
移出去的原最低位自动进入 CF,本例中 CF=1五、掩码操作(Mask)
掩码的核心思想:用一个「模板」通过 and/or/xor 来精确控制某些位。
5.1 清零某些位(and 掩码)
目标:?011 011? → 0011 0110
最高位及最低位清零,其余位不变设此数在 ah 中:
and ah, 01111110B ; 01111110B 是 mask(掩码), 起过滤作用 ?011 011?
0111 1110 and)
---------------
0011 01105.2 置1某些位(or 掩码)
目标:?011 011? → 1011 0111
最高位及最低位置1,其余位不变设此数在 ah 中:
or ah, 10000001B ; 10000001B 是 mask(掩码), 起过滤作用 ?011 011?
1000 0001 or)
---------------
1011 01115.3 翻转某些位(xor 掩码)
目标:1011 0110 → 0011 0111
最高位及最低位反转,其余位不变设此数在 ah 中:
xor ah, 10000001B ; 10000001B 是 mask(掩码), 起过滤作用 1011 0110
1000 0001 xor)
---------------
0011 01115.4 异或的可逆性
设 z = x ^ y,则一定有:x = z ^ y 且 y = z ^ x。
举例:
x = 1011 0110B
y = 1100 0011B xor)
z = 0111 0101B
验证: z ^ y = 0111 0101B ^ 1100 0011B = 1011 0110B = x六、循环移位
6.1 rol 8 位循环左移
mov al, 10110110B
rol al, 1 ; 把AL循环左移1位, rol: rotate left
; al = 01101101B, CF = 16.2 rol 32 位循环左移
mov eax, 12345678h
rol eax, 4
; 移位前: 0001 0010 0011 0100 0101 0110 0111 1000
; 移位后: 0010 0011 0100 0101 0110 0111 1000 0001还有带进位的循环移位:
rcl:带进位的循环左移(rotate left through carry)rcr:带进位的循环右移(rotate right through carry)
七、实战:进制转换输出
7.1 输出 32 位整数的十六进制表示(老师 2.asm)
思路:每轮 rol eax, 4 把最高 4 位挪到最低 4 位,用 and eax, 0Fh 提取出来,转成字符输出,重复 8 轮。
;把32位整数转化成16进制格式输出
.386
code segment use16
assume cs:code
main:
mov eax, 12345678h
mov cx, 8
again:
rol eax, 4 ; eax = 23456781h
;push eax ; 把eax的值压入堆栈
mov ebx, eax ; ebx = eax, 备份eax的值到ebx中
and eax, 1111B ; 或写成 and eax, 0Fh
cmp eax, 10
jl is_digit
is_alpha:
sub eax, 10
add eax, 'A'
jmp output
is_digit:
add eax, '0'
output:
mov ah, 2
mov dl, al ; 不能写成mov dl, eax; 因为操作数必须等宽
int 21h
mov eax, ebx ; 恢复eax的值
;pop eax ; 从堆栈中弹出eax即恢复eax
sub cx, 1
jnz again
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main运行结果:输出 12345678。
7.2 输出 32 位整数的二进制表示(老师 3.asm)
同理,每轮 rol eax, 1 移出 1 位,提取后转字符,重复 32 轮:
;把32位整数转化成二进制格式输出
.386
code segment use16
assume cs:code
main:
mov eax, 12345678h
mov cx, 32
again:
rol eax, 1 ; eax = 23456781h
;push eax ; 把eax的值压入堆栈
mov ebx, eax ; ebx = eax, 备份eax的值到ebx中
and eax, 1
add eax, '0'
output:
mov ah, 2
mov dl, al ; 不能写成mov dl, eax; 因为操作数必须等宽
int 21h
mov eax, ebx ; 恢复eax的值
;pop eax ; 从堆栈中弹出eax即恢复eax
sub cx, 1
jnz again
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main要点:
mov dl, al不能写成mov dl, eax,因为操作数必须等宽(都是 8 位)。
八、本周易错点
- 有符号比较误用 jb/ja,无符号比较误用 jl/jg
- 提取位/半字节时忘记备份原寄存器(rol 后 eax 已变)
mov dl, eax这种操作数宽度不匹配的错误- 忘记 idiv 前需要 cwd/cdq 扩展符号位
and eax, 0Fh和and eax, 1111B等价,不要漏掉进制后缀