感谢曹老师的PPT、《汇编语言程序设计教程》(卜艳萍,清华出版社),仔细阅读后,自己提取并总结了这些技巧或方法。文中若有纰漏之处,恳请读者通过邮件向我指正。

位运算技巧在汇编程序中的应用

  • 寄存器初始化

    举例使用:

    1
    xor ax, ax ;将ax寄存器置零
  • 判断寄存器是否为0

    假如我们刚刚进行完除法运算,需要判断余数(默认存放在 DX 中)是否为0,我们可以对 DX 自身按位与,触发标志位影响,举例使用如下:

    1
    2
    and dx, dx
    jz next
  • 求反的位同 1 异或,维持不变的位同 0 异或

  • 检查某些位是否同时1

    举例:检查 CX 中的第1,6和11位中是否同时为1

    1
    2
    3
    4
    not cx ;先取反,如果所求位是1的话,此时转为0
    test cx, 0842h ;对其他位置0,对所求位同1按位与
    jz yes ;如果结果是0,说明所求位都转变成0了,即所求位原来同时为1
    not cx ;记得取补恢复

其他汇编技巧

  • 判断某个数(假定存放在 ax)是正数,还是零,还是负数

    1
    2
    3
    4
    add ax, 0
    js DON3 ;sf=1, 负数
    je DON2 ;zf=1,零
    ....

16进制数转换为ASCII码

DOS功能调用的2号功能的输出,需要传入一个ASCII码的字符。然而我们现在只有一个16进制数,如何得到对应的ASCII码呢?

方法一:查表法

(1)要点:

  • 换码指令—— XLAT:将BX指定的缓冲区中、AL 指定的位移处的一个字节数据取出赋给 AL ,实际相当于 (AL) = (DS:(BX+AL)) 。注意,不是单纯地赋予 AL+BX ,而是对应地址的值。

    换码指令执行前,一般在主存建立一个字节量表格(如下代码4-6行),内含要转换成的目的代码表格首地址存放于 BXAL 存放相对表格首地址的位移量

    换码指令执行后,将 AL 寄存器内容转换为目的代码

(2)代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
assume cs:codesg, ds:datasg

datasg segment
ASCII db 30h,31h,32h,33h,34h,35h
db 36h,37h,38h,39h ;0~9的ASCII码
db 41h,42h,43h,44h,45h,46h ;A~F的ASCII码
hex db 09h ;任意设定了一个待转换的一位16进制数,这里以'0f'为例
datasg ends

codesg segment
start:
mov ax, datasg
mov ds, ax

mov bx, offset ASCII ;bx存储标号ASCII的偏移地址(将其作为基准值)
mov al, hex ;将待转换的16进制数放到低位寄存器
and al, 0fh ;按位与,对8位的前4位清0(因为实验只要求输出后4位)
xlat ;换码:al<- DS:[BX+AL] 基准值BX(ASCII的offset)+位移量AL(待转换)
mov dl, al ;入口参数:dl<-al
mov ah, 2 ;02号DOS功能调用
int 21h ;显示一个ASCII码字符

mov ah, 4ch
int 21h
codesg ends

end start

(3)内存中存储的数据:

(4)运行结果:

hex赋值0fh09h
运行结果

方法二:直接转换法

下面演示的是两位16进制数(即8位二进制,1个字节大小,可以存放到 8位寄存器 如 AL等)

(1)分析:

我们观察一下 ASCII 码表,发现090-9的ASCII码(二进制8位)下,前8位具有共同前缀 0011 ,而末8位恰好就是090-9的二进制表示!那 16 进制下的AFA-F呢?我们只需要特判即可,如果该数字的ASCII码不超过 39H (即数字9),那么直接输出即可;若超过(说明数字是A-F),那么直接对其加上7即可

由于两位16进制数即是8位二进制数,故我们需要先对高4位二进制数进行转换,再对低4位二进制数进行转换。

(2)代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
datasg segment
datasg ends

stacks segment
db 256 dup(?)
stacks ends

codesg segment
assume cs:codesg, ds:datasg, ss:stacks
start:
mov ax, datasg
mov ds, ax
mov al, 0fah; //将你需要转换的两位16进制放到al寄存器中

push ax ;暂存ax
mov dl, al
mov cl, 4 ;用于shr指令,代表移位4位
shr dl, cl ;dl先取出al的高4位
call transASCII
pop dx ;将之前存的ax给pop出来
and dx, 000fh ;dl取出取出al的低4位
call transASCII

mov ah, 4ch
int 21h

;子程序实现转换
transASCII proc far
or dl, 30h ;对该串的前4位按位或上"0011"(ASCII码中数字的前缀串)
cmp dl, 39h ;39H是数字9的ASCII码16进制
jbe output ;小于等于39(即数字没超过9),直接输出
add dl, 7 ;大于等于39 (即数字为A-F),需要对ASCII码加上7
output:
mov ah, 2
int 21h
ret
transASCII endp

codesg ends
end start


(3)运行结果:

AL赋值fah9ch
运行结果

多分支结构——地址表法

(1)要点:

  • 当程序分支较多时,可使用地址表(跳转表)来实现程序分支。我们在数据段事先安排一个地址表,里面连续存放的是一系列跳转地址,即各分支程序的入口地址

  • 由于使用 DOS 功能调用的1号功能,输入只能是一个字节大小的字符,同时是以二进制的ASCII码存入 AL 中。故需将 AL 存下 ASCII 码转换为对应的数字。(在下面的第30行代码)

  • ⭐ 因为一个段最大存储空间是6464 KB,偏移地址故使用 16 位表示。因此需要用 2 个字节存储单元 (1个字)去储存偏移地址。对于这个地址表而言,要两个字节、两个字节…如此去寻找表中的每个元素。(在下面的第32行代码)

(2)代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
datasg segment
str0 db 0dh,0ah,'input number (1-3):',0dH,0ah,'$'
str1 db 0dh,0ah,'chapter1: introduction',0dH,0ah,'$'
str2 db 0dh,0ah,'chapter2: designing method',0dH,0ah,'$'
str3 db 0dh,0ah,'chapter3: experiment',0dH,0ah,'$'
table dw disp1, disp2, disp3 ;在数据段中定义各个标号的偏移地址,相当于offset disp1,此时相应内存单元即存了(16位)偏移量
datasg ends

stacks segment
;
stacks ends

codesg segment
assume cs:codesg, ds:datasg, ss:stacks
start:
mov ax, datasg
mov ds, ax
input:
mov ah, 09h ;输出字符串
mov dx, offset str0
int 21h

mov ah, 01h
int 21h
cmp al, '1'
jb input ;如果小于(below)1,则重新输入
cmp al, '3'
ja input ;如果大于(above)3,则重新输入

and ax, 000fh; //16位ax只保留最后4位,从而实现ASCII转数字(具体见上)
dec ax ; 因为地址表table里面的元素从0开始计数
shl ax, 1; 左移一位,因为16位偏移地址占用2个字节单元
mov bx, ax
jmp table[bx]; //寄存器相对寻址,(ip)=table+bx


disp1:
mov dx, offset str1
jmp output
disp2:
mov dx, offset str2
jmp output
disp3:
mov dx, offset str3
jmp output
output:
mov ah, 09h
int 21h
mov ah, 4ch
int 21h
codesg ends
end start

逻辑尺

逻辑尺,其实就是将有限个算式中的加减运算符用 01 进行区分,由此得到有限长度的 01 串。

(1)题目

给定数组x (x1,x2,...,x10)x\ (x_1,x_2,...,x_{10})y (y1,y2,...,y10)y\ (y_1,y_2,...,y_{10}) ,要求计算下面的z (z1,z2,...,z10)z\ (z_1,z_2,...,z_{10}),具体算式如下:

{z1=x1+y1z2=x2+y2z3=x3y3z4=x4y4z5=x5y5z6=x6+y6z7=x7y7z8=x8y8z9=x9+y9z10=x10+y10\begin{cases} z_1 &= x_1 + y_1 \\ z_2 &= x_2 + y_2 \\ z_3 &= x_3 - y_3 \\ z_4 &= x_4 - y_4 \\ z_5 &= x_5 - y_5 \\ z_6 &= x_6 + y_6 \\ z_7 &= x_7 - y_7 \\ z_8 &= x_8 - y_8 \\ z_9 &= x_9 + y_9 \\ z_{10}&= x_{10} + y_{10} \\ \end{cases}

(2)分析

注意只有1010项,每一项的zi(1i10)z_i(1\leq i\leq 10)都是由xix_iyiy_i 进行加或减的运算,我们用 1 表示减法、0 表示加法,因此,从第一条式(在低位)到第10条式(在高位)的加减符号便能够得到一条 01 串:0000,0000,1101,1100b(16进制为 00dch),接着我们只需要循环依次遍历ziz_i 便能够得出结果了。

(3)代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
datasg segment
x dw x1,x2,x3,x4,x5,x6,x7,x8,x9,x10 ;注意,存放的应该是x数组每个元素的值,这里写成x1,x2,...只是占个位
y dw y1,y2,y3,y4,y5,y6,y7,y8,y9,y10 ;同上理y1,y2,...只是占个位,实际情况是要填上数字的
z dw z1,z2,z3,z4,z5,z6,z7,z8,z9,z10 ;同上理z1,z2,...只是占个位,实际情况是要填上数字的
logic_rule dw 00dch ; 0000,0000,1101,1100,其中1表示减法,0表示加法
datasg ends

stacks segment
;
stacks ends

codesg segment
assume cs:codesg, ds:datasg, ss:stacks
start:
mov ax, datasg
mov ds, ax

mov bx, 0
mov cx, 10 ;10个元素
mov dx, logic_rule
next:
mov ax, x[bx]
shr dx, 1 ;右移,使得CF标记逻辑尺的最低位
jc subtract ;当前最低位为1,进行减法运算
add ax, y[bx];当前最低位为0,进行加法运算
jmp restore
subtract:
sub ax, y[bx]
restore:
mov z[bx], ax;将结果存到z数组中
add bx, 2 ;找到数组下一个字单元
loop next

mov ah, 4ch
int 21h
codesg ends
end start

其他算法

  • 阶乘运算(递归实现)

    《汇编语言程序设计教程》(卜艳萍,清华出版社)P175

  • 辗转相除法

    《汇编语言程序设计教程》(卜艳萍,清华出版社)P165

  • 冒泡排序

    《汇编语言程序设计教程》(卜艳萍,清华出版社)P157