汇编语言学习记录02之程序设计技巧
位运算技巧在汇编程序中的应用
-
寄存器初始化
举例使用:
xor ax, ax ;将ax寄存器置零
-
判断寄存器是否为0
假如我们刚刚进行完除法运算,需要判断余数(默认存放在
DX
中)是否为0,我们可以对DX
自身按位与,触发标志位影响,举例使用如下:and dx, dx jz next
-
求反的位同
1
异或,维持不变的位同0
异或 -
检查某些位是否同时为
1
举例:检查
CX
中的第1,6和11位中是否同时为1
not cx ;先取反,如果所求位是1的话,此时转为0 test cx, 0842h ;对其他位置0,对所求位同1按位与 jz yes ;如果结果是0,说明所求位都转变成0了,即所求位原来同时为1 not cx ;记得取补恢复
16进制数转换为ASCII码
DOS功能调用的2号功能的输出,需要传入一个ASCII码的字符。然而我们现在只有一个16进制数,如何得到对应的ASCII码呢?
方法一:查表法
零、适用范围:
适用于只有一位的16进制数(即4位二进制),不适用于两位或两位以上的16进制数。
一、要点:
-
换码指令——
XLAT
:将BX
指定的缓冲区中、AL
指定的位移处的一个字节数据取出赋给AL
,实际相当于(AL) = (DS:(BX+AL))
。注意,不是单纯地赋予AL+BX
,而是对应地址的值。换码指令执行前,一般在主存建立一个字节量表格(如下代码4-6行),内含要转换成的目的代码表格首地址存放于
BX
,AL
存放相对表格首地址的位移量。换码指令执行后,将
AL
寄存器内容转换为目的代码。
二、代码:
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
三、内存中存储的数据:
四、运行结果:
hex赋值 | 0fh |
09h |
---|---|---|
运行结果 |
方法二:位运算法
零、适用范围:
适用于多位16进制数,下面演示的是两位16进制数(即8位二进制,1个字节大小,可以存放到 8位寄存器 如 AL
等)
一、分析:
我们观察一下 ASCII 码表,发现\(0-9\)的ASCII码(二进制8位)下,前8位具有共同前缀 0011
,而末8位恰好就是\(0-9\)的二进制表示!那 16 进制下的\(A-F\)呢?我们只需要特判即可,如果该数字的ASCII码不超过 39H
(即数字9),那么直接输出即可;若超过(说明数字是A-F),那么直接对其**加上 \(7\) **即可
由于两位16进制数即是8位二进制数,故我们需要先对高4位二进制数进行转换,再对低4位二进制数进行转换。
二、代码:
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 dl, 0fh ;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
三、运行结果:
AL 赋值 |
fah |
9ch |
---|---|---|
运行结果 |
多分支结构——地址表法
一、要点:
-
当程序分支较多时,可使用地址表(跳转表)来实现程序分支。我们在数据段事先安排一个地址表,里面连续存放的是一系列跳转地址,即各分支程序的入口地址。
-
由于使用 DOS 功能调用的1号功能,输入只能是一个字节大小的字符,同时是以二进制的ASCII码存入 AL 中。故需将 AL 存下 ASCII 码转换为对应的数字。(在下面的第30行代码)
-
⭐ 因为一个段最大存储空间是 \(64\) KB,偏移地址故使用 16 位表示。因此需要用 2 个字节存储单元 (1个字)去储存偏移地址。对于这个地址表而言,要两个字节、两个字节…如此去寻找表中的每个元素。(在下面的第32行代码)
二、代码:
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
逻辑尺
逻辑尺,其实就是将有限个算式中的加减运算符用 0
或 1
进行区分,由此得到有限长度的 01
串。
一、题目
给定数组 \(x\ (x_1,x_2,...,x_{10})\) 和 \(y\ (y_1,y_2,...,y_{10})\) ,要求计算下面的 \(z\ (z_1,z_2,...,z_{10})\),具体算式如下:
二、分析
注意只有\(10\)项,每一项的\(z_i(1\leq i\leq 10)\)都是由 \(x_i\) 与 \(y_i\) 进行加或减的运算,我们用 1
表示减法、0
表示加法,因此,从第一条式(在低位)到第10条式(在高位)的加减符号便能够得到一条 01
串:0000,0000,1101,1100b
(16进制为 00dch
),接着我们只需要循环依次遍历 \(z_i\) 便能够得出结果了。
三、代码
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