Skip to content

第3讲:算术运算、位运算与进制转换

本周核心:掌握有符号/无符号运算的区别,熟练使用位运算与移位,能写出 32 位整数转十六进制/二进制的输出程序。


一、算术运算

老师 1.asm 知识点总讲义全部内容如下。

1.1 加减法

asm
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 — 双操作数有符号乘法

asm
; --- 两个有符号数相乘,不溢出 ---
mov ax, 2
mov bx, 3
imul ax, bx      ; ax = ax * bx = 2*3 = 0006h
                 ; OF=0, CF=0

当结果超出寄存器宽度时,会被截断

asm
; --- 溢出示例 ---
mov ax, 1234h
mov bx, 100h
imul ax, bx      ; ax = 123400h 无法保存
                 ; ax = 3400h(高位被截断)
                 ; OF=1, CF=1

mul bx — 无符号乘法(单操作数形式)

16 位 × 16 位 → 32 位结果存在 dx:ax 中:

asm
mov ax, 2
mov bx, 3
mul bx           ; ax * bx = dx、ax
                 ; dx=0000h, ax=0006h

imul 的三种形式(老师 imul.asm 的完整补充)

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 的完整例子:

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   jne

2.2 同一比特模式,不同语义得出相反结论

asm
mov ax, 0FFFFh      ; 有符号:FFFFh = -1;无符号:FFFFh = 65535
mov bx, 1
cmp ax, bx
jl ax_is_less_than_bx   ; -1 < 1,会发生跳转!
asm
mov ax, 0FFFFh      ; 65535
mov bx, 1
cmp ax, bx
jb ax_is_below_bx   ; 65535 > 1,不会发生跳转!

2.3 16 位计数器减到 0 以下的陷阱

asm
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() = 循环右移库函数。汇编有对应的 rolror 指令。

下面逐个演示老师 1.asm 中的完整例子。

3.2 按位与(and)

asm
mov ah, 10110110B    ; 或写成 mov ah, 0B6h(B 是二进制常数的后缀)
mov bh, 01011010B
and ah, bh           ; ah = ah & bh = 00010010B = 12h
  10110110
  01011010  and)
-----------
  00010010

3.3 按位或(or)

asm
mov ah, 10110110B    ; 或写成 mov ah, 0B6h
mov bh, 01011010B
or ah, bh            ; ah = ah | bh = 11111110B = 0FEh
  10110110
  01011010  or)
-----------
  11111110

3.4 按位异或(xor)

asm
mov ah, 10110110B    ; 或写成 mov ah, 0B6h
mov bh, 01011010B
xor ah, bh           ; ah = ah ^ bh = 11101100B = 0ECh
  10110110
  01011010  xor)
-----------
  11101100

3.5 按位取反(not)

asm
mov ah, 10110110B    ; 或写成 mov ah, 0B6h
not ah               ; ah = 01001001B = 49h
  10110110  not)
-----------
  01001001

3.6 清除寄存器的最快方法

asm
xor ax, ax           ; ax = 0,比 mov ax, 0 更快
xor eax, eax         ; eax = 0

四、移位指令

4.1 逻辑左移 shl

asm
mov ah, 3
shl ah, 1            ; 左移1位, shl: shift left 逻辑左移
  0000 0011   (3, 移位前)
  0000 0110   (6, 移位后)
  移出去的原最高位自动进入 CF,本例中 CF=0
  左移1位相当于乘以2

shlsal 是同一指令的别名。

4.2 逻辑右移 shr

asm
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 中:

asm
and ah, 01111110B    ; 01111110B 是 mask(掩码), 起过滤作用
  ?011 011?
  0111 1110  and)
---------------
  0011 0110

5.2 置1某些位(or 掩码)

目标:?011 011? → 1011 0111
     最高位及最低位置1,其余位不变

设此数在 ah 中:

asm
or ah, 10000001B     ; 10000001B 是 mask(掩码), 起过滤作用
  ?011 011?
  1000 0001  or)
---------------
  1011 0111

5.3 翻转某些位(xor 掩码)

目标:1011 0110 → 0011 0111
     最高位及最低位反转,其余位不变

设此数在 ah 中:

asm
xor ah, 10000001B    ; 10000001B 是 mask(掩码), 起过滤作用
  1011 0110
  1000 0001  xor)
---------------
  0011 0111

5.4 异或的可逆性

设 z = x ^ y,则一定有:x = z ^ yy = 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 位循环左移

asm
mov al, 10110110B
rol al, 1            ; 把AL循环左移1位, rol: rotate left
                     ; al = 01101101B, CF = 1

6.2 rol 32 位循环左移

asm
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 轮。

asm
;把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 轮:

asm
;把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 位)。


八、本周易错点

  1. 有符号比较误用 jb/ja,无符号比较误用 jl/jg
  2. 提取位/半字节时忘记备份原寄存器(rol 后 eax 已变)
  3. mov dl, eax 这种操作数宽度不匹配的错误
  4. 忘记 idiv 前需要 cwd/cdq 扩展符号位
  5. and eax, 0Fhand eax, 1111B 等价,不要漏掉进制后缀