《汇编语言》学习笔记-3
注:本文档为“《汇编语言(第3版) 》王爽著”阅读过程中记的笔记。
参考视频:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili
11 标志寄存器
CPU内部的标记寄存器具有以下作用:
- 用来存储相关指令的某些执行结果;
- 用来为CPU执行相关指令提供行为依据;
- 用来控制CPU的相关工作方式。
8086CPU的标志寄存器是16位的,其中存储的信息通常被称为程序状态字(PSW)。
flag寄存器的按位起作用的,每一个位有专门的含义,记录特定的信息。
其中,第1、3、5、12、13、14、15位没有使用。
debug.exe的的标志寄存器:
直接访问标志寄存器的方法:
- pushf:将标志寄存器的值压栈
- popf:从栈中弹出数据,送入标志寄存器中。
11.1 Z-Flag(Zero Flag)标志
flag的第6位是Z-Flag,零标志位。它记录相关指令执行后,其结果是否位0。
- 如果结果为0,那么Z-Flag=1,’1‘表示逻辑真;
- 如果结果不为0,那么Z-Flag=0,’0‘表示逻辑假。
举例:
mov ax, 1
sub ax, 1 ; 执行后,结果为0,Z-Flag=1
mov ax, 2
sub ax, 1 ; 执行后,结果不为0,Z-Flag=0
8086CPU指令集中,有些指令影响标志寄存器,比如:add、sub、mul、div、inc、or、and等,大部分是运算指令,进行逻辑或算术运算。
有些指令对标志寄存器没有影响,比如:pop、push、mov,大部分是传送指令。
11.2 P-Flag(Parity Flag)标志
flag的第2位是PF位,奇偶校验位,它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。
- 如果1的个数为偶数,P-Flag=1;
- 如果1的个数为奇数,P-Flag=0。
举例:
mov al, 1
add al, 10 ; 执行后,结果为11,P-Flag=0
mov al, 1
or al, 2 ; 执行后,结果为3,P-Flag=1
debug.exe中:pe表示偶校验,po表示奇校验。
11.3 S-Flag(Sign Flag)标志
flag的第7位是S-Flag,符号标志位,它记录相关指令执行后,其结果是否为负。
- 如果结果为负,S-Flag=1;
- 如果结果非负,S-Flag=0。
计算机中通常用补码来表示有符号数据,计算机中的一个数据可以看作是有符号数,也可以看作无符号数。比如:
00000001B:可看作无符号数1,或者符号数+1;
10000001B:可以看作为无符号数129,或者有符号数-127。
CPU执行add等运算指令时,已经包含了正负两种含义,也将得到用同一种信息来记录的两种结果,关键在于程序需要哪一种结果。
S-Flag标志就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,S-Flag的值没有意义。
也就是说,CPU在执行add等指令时,是必然要影响到SF标志位的值,是否需要这个值,看指令所进行的运算。
mov al, 10000001B
add al, 1 ; 执行结果为10000010B,S-Flag=1,表示如果指令进行的是有符号运算,则结果为负。
mov al, 10000001B
add al, 01111111B ; 执行后,结果为0,SF=0,如果指令进行的是有符号数运算,则结果非负。
某些指令将影响标志寄存器中的多个标志位,这些被影响的标记位比较全面的记录了指令的执行结果,为相关的处理提供了所需的依据。
debug.exe中:ng表示负数,pl表示正数。
11.4 C-Flag(Carry Flag)标志
flag的第0位是CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高位有效位向更高位的进位值,或从更高位的借位值。
CF记录指令执行后,
- 有进位或借位,C-Flag=1;
- 无进位或借位,C-Flag=0。
对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相当于最高有效位的更高位。
mov al, 98H
add al, al ; 执行后,(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值
add al, al ; 执行后,(al)=60H,CF=0,CF记录了从最高有效位向更高位的进位值
当两个数据做减法的时候,有可能向更高位借位。C-Flag也可以用来记录这个借位值。
mov al, 97h
sub al, 98h ; 执行后,(al)=FFH,CF=1,CF记录了向更高位的借位值
sub al, al ; 执行后,(al)=0,CF=0,CF记录了向更高位的借位值
11.5 O-Flag(Overflow Flag)标志
溢出:在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。
机器所能表示的范围:比如指令运算的结果用8位寄存器或内存单元存放,对于8位有符号数据,范围就是-128~127;16位有符号数据,范围就是-32768~32767。
如果运算结果(有符号数)超出了机器所能表达的范围,将产生溢出。
mov al, 98
add al, 99 ; 执行后将产生溢出,执行结果为197,超过了127
mov al, 0F0h ; F0H,为有符号数-16的补码
add al, 088H ; 88H,为有符号数-120的补码,执行后结果为-136,溢出
由于在进行有符号数运算的时候,可能发生溢出而造成错误的结果,则CPU需要对指令执行后是否产生溢出进行记录。
O-Flag就是溢出标记位,一般情况下,O-Flag记录了有符号数运算的结果是否发生了溢出。
- 如果发生溢出,O-Flag=1;
- 如果没有溢出,O-Flag=0。
C-Flag和O-Flag的区别:C-Flag是对无符号数运算有意义的标志位,O-Flag是对有符号数运算有意义的标志位。
mov al, 98
add al, 99 ; 执行结果197(C5H),C-Flag=0,O-Flag=1
mov al, 0F0H
add al, 88H ; 执行结果178H,C-Flag=1,O-Flag=1((-16)+(-120)=-136)
C-Flag和O-Flag的区别:
- C-Flag是对无符号数运算有意义的进/借位标志位;
- O-Flag是对有符号数运算有意义的溢出标志位。
11.6 adc指令
adc是带进位加法指令,利用C-Flag上记录的进位值。
指令格式:
adc 操作对象1, 操作对象2
功能:操作对象1=操作对象1+操作对象2+C-Flag
举例:
adc ax, bx ; (ax)=(ax)+(bx)+C-Flag
mov ax, 2
mov bx, 1
sub bx, ax ; (C-Flag)=1
adc ax, 1 ; (ax)=4,相当于(ax)+1+C-Flag=2+1+1=4
mov ax, 1
add ax, ax
adc ax, 3 ; (ax)=5,相当于(ax)+1+C-Flag=2+3+0=4
mov al, 98H
add al, al ; 98H+98H=130H
adc al, 3 ; (ax)=34H,相当于(al)+3+C-Flag=30H+3+1=34H
C-Flag的值的含义:在执行adc指令的时候CF的含义是由adc指令前面的指令决定的,如果C-Flag的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。
加法可以分两步来进行:①低位相加;②高位相加再加上低位相加产生的进位值。
CPU提供adc指令的目的:进行加法的第二步运算,adc指令和add指令相配合就可以对更大的数据进行加法运算。
实验:计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。
思路:由于两个数据都大于16位,用add指令无法进行计算,将计算分为2步进行,先将低16位相加,然后将高16位和进位值相加。
mov ax, 001EH
mov bx, 0F000H
add bx, 1000H ; 先进行低16位相加
adc ax, 0020H ; 再进行高位相加
实验:计算1EF0001000H+2010001EF0H,结果放在ax(高16位)、bx(次高16位)、cx(低16位)中。
思路:
- 先将低16位相加,完成后,C-Flag中记录本次相加的进位值;
- 再将次高16位和C-Flag(来自低16位的进位值)相加,完成后,C-Flag中记录本次相加的进位值;
- 最后高16位和C-Flag(来自次高16位的进位值)相加,完成后,C-Flag中记录本次相加的进位值。
mov ax, 001EH
mov bx, 0F000H
mov cx, 1000H
add cx, 1EF0H
adc bx, 1000H
adc ax, 0020H
实验:128位的数据相加
11.7 sbb指令
sbb是带借位减法指令,它利用了C-Flag位上记录的借位值。
指令格式:
sbb 操作对象1, 操作对象2
功能:操作对象1=操作对象1-操作对象2-C-Flag
sbb ax, bx ; (ax)=(ax) - (bx) - C-Flag
sbb指令执行后,将对C-Flag位进行设置。利用sbb指令可以对任意大的数据进行减法运算。
实验:计算003E1000H-00202000H,结果放在ax,bx中。
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H ; 借位值存放在C-Flag
sbb ax, 0020H
11.8 cmp指令
cmp是比较指令,功能相当于减法指令,只是不保存结果。
cmp指令执行后,将对标志寄存器产生影响。其它相关指令通过识别这些被影响的标志寄存器来得知比较结果。
指令格式:
cmp 操作对象1, 操作对象2
功能:计算操作对象1-操作对象2,但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。
CPU在执行cmp指令时,包含两种含义:进行无符号数运算和进行有符号数运算。
(1)进行无符号数的比较
cmp ax, bx ; 逻辑含义是比较ax和bx中的值
比较关系 | (ax) ? (bx) | (ax) - (bx)的特点 | 标志寄存器 |
---|---|---|---|
等于 | (ax)=(bx) | (ax)-(bx)=0 | Z-Flag=1 |
不等于 | (ax)≠(bx) | (ax)-(bx)≠0 | Z-Flag=0 |
小于 | (ax)<(bx) | (ax)-(bx)将产生借位 | C-Flag=1 |
大于等于 | (ax)≥(bx) | (ax)-(bx)不必借位 | C-Flag=0 |
大于 | (ax)>(bx) | (ax)-(bx)既不借位,结果又不为0 | C-Flag=0并且Z-Flag=0 |
小于等于 | (ax)≤(bx) | (ax)-(bx)或者借位,或者结果为0 | C-Flag=1或Z-Flag=1 |
(2)进行有符号数的比较
问题:逻辑上真正的正负和实际结果的正负带来的问题。
mov ah, 08AH
mov bh, 070H
cmp ah, bh ; 结果S-Flag=0
运算(ah)-(bh)实际得到的结果是1AH,但在逻辑上,结果应该是:(-118)-112=-230。S-Flag不能说明在逻辑上运算所得到的结果。
cmp ah, bh
- 如果S-Flag=1,而O-Flag=0。
O-Flag=0,说明没有溢出,逻辑上真正的正负=实际结果的正负;
S-Flag=1,说明实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。
- 如果S-Flag=1,而O-Flag=1。
O-Flag=1,说明有溢出,逻辑上真正的正负≠实际结果的正负;
S-Flag=1,说明实际结果为负。实际结果为负,而又有溢出。
如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。所以(ah)>(bh)。
- 如果S-Flag=0,而O-Flag=1。
O-Flag=1,说明有溢出,逻辑上真正的正负≠实际结果的正负;
S-Flag=1,说明实际结果为非负。而O-Flag=1说明有溢出,则结果非0,所以实际结果为正。
如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。所以(ah)<(bh)。
- 如果S-Flag=0,而O-Flag=0。
O-Flag=0,说明没有溢出,逻辑上真正的正负=实际结果的正负;
S-Flag=0,说明实际结果非负,所以逻辑上真正的结果为非负,所以(ah)≥(bh)。
比较关系 | (ax) ? (bx) | (ax) - (bx)的特点 | 标志寄存器 |
---|---|---|---|
等于 | (ax)=(bx) | (ax)-(bx)=0 | Z-Flag=1 |
不等于 | (ax)≠(bx) | (ax)-(bx)≠0 | Z-Flag=0 |
小于 | (ax)<(bx) | (ax)-(bx)为负,且不溢出 | S-Flag=1且O-Flag=0 |
大于 | (ax)>(bx) | (ax)-(bx)为负,且有溢出 | S-Flag=1且O-Flag=1 |
大于等于 | (ax)≥(bx) | (ax)-(bx)为非负,且无溢出 | S-Flag=0且O-Flag=0 |
小于等于 | (ax)≤(bx) | (ax)-(bx)为非负,且有溢出 | S-Flag=0且O-Flag=1 |
11.9 检测比较结果的条件转移指令
“转移”:指的是能够修改IP;
"条件":指的是可以根据某种条件,决定是否修改IP。
因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种:根据无符号数的比较结果进行转移的条件转移指令(检测Z-Flag和C-Flag的值)和根据有符号数的比较结果进行转移的条件转移指令(检测S-Flag、O-Flag和Z-Flag)。
常用的根据无符号数的比较结果进行转移的条件转移指令:
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移(equal) | Z-Flag=1 |
jne | 不等于则转移(not equal) | Z-Flag=0 |
jb | 低于则转移(below) | C-Flag=1 |
jnb | 不低于则转移(not below) | C-Flag=0 |
ja | 高于则转移(above) | C-Flag=0且Z-Flag=0 |
jna | 不高于则转移(not above) | C-Flag=1或Z-Flag=1 |
根据单个标志位转移的指令:
指令 | 含义 | 检测的相关标志位 |
---|---|---|
jz | 结果为0(Zero) | Z-Flag=1 |
jnz | 结果不为0(Not Zero) | Z-Flag=0 |
js | 结果为负(Sign) | S-Flag=1 |
jns | 结果非负(Not Sign) | S-Flag=0 |
jo | 结果溢出(Overflow) | O-Flag=1 |
jno | 结果非溢出(Not Overflow) | O-Flag=0 |
jp | 奇偶位为1(Parity) | P-Flag=1 |
jnp | 奇偶位不为1(Not Parity) | P-Flag=0 |
jc | 有借位(Carry) | C-Flag=1 |
jnc | 无借位(Not Carry) | C-Flag=0 |
根据有符号数比较结果进行转移的指令:
指令 | 含义 | 测试条件 |
---|---|---|
jl/jnge | 小于则转移(less) | S-Flag=1且O-Flag=0 |
jnl/jge | 不小于则转移(greater equal) | S-Flag=0且O-Flag=0 |
jle/jnp | 小于等于则转移(less equal) | S-Flag=0且O-Flag=1 |
jnle/jg | 大于则转移(greater) | S-Flag=1且O-Flag=1 |
实验:如果(ah)=(bh),则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)
; 双分支结构的实现
cmp ah, bh
je s
add ah, bh
jmp short ok
s:
add ah, ah
ok: ret
条件转移指令的使用:jxxx系列指令和cmp指令配置,构造条件转移指令:
- 不必再考虑cmp指令对相关标志位的影响和jxxx指令对相关标志位的检测。
- 可以直接考虑cmp和jxxx指令配置使用时表现出来的逻辑含义。
实验:统计data段中各种值(值为8,值大于8,值小于8)数据的个数。
assume cs:codesg
data segment
db 8, 11, 8, 1, 8, 5, 63, 38
data ends
codesg segment
mov ax, data
mov ds, ax
mov bx, 0
mov ax, 0
mov cx, 8
s: cmp byte ptr [bx], 8
jne next ; 判断不等于8
inc ax ; 相等加1
next:
inc bx
loop s
mov ax, 4c00h
int 21h
codesg ends
end start
判断大于8的数:
s: cmp ptr [bx], 8
jna next
inc ax
判断小于8的数的:
s: cmp ptr [bx], 8
jnb next
inc ax
11.10 D-Flag(Direction Flag)标志和串传送指令
flag的第10位是D-Flag,方向标志位。在串处理指令中,控制每次操作后si、di的递减。
D-Flag=0 ; 每次操作后si、di递增
D-Flag=1 ; 每次操作后si、di递减
(1)串传送指令:
movsb
功能:执行movsb指令相当于进行下面几步操作:
-
((es)*16+(di)*16)=((ds)*16+(si))
-
如果D-Flag=0,则(si)=(si)+1、(di)=(di)+1
如果D-Flag=1,则(si)=(si)-1、(di)=(di)-1
也就是将ds:si执行的内存单元中的字节送入es:di中,然后根据标志寄存器D-Flag的值,将si和di递增或递减。
(2)串传送一个字的指令:
movsw
功能:将ds:si执行的内存单元中的字送入es:di中,然后根据标志寄存器D-Flag的值,将si和di递增2或递减2。
(3)rep指令(和movsb或movsw配合使用)
rep movsb
rep movsw
功能:根据cx的值,重复执行后面的串行传送指令,可以循环实现(cx)个字符(字节或字)的传送。
用汇编语法描述rep movsb的功能:
s: movsb
loop s
(4)对D-Flag位进行配置的指令
cld指令(clear):将标志寄存器的D-Flag位置为0
std指令(setup):将标志寄存器的D-Flag位置为1
实验:将data段中的第一个字符串复制到它后面的空间中。
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
code segment
mov ax, data
mov ds, ax
mov si, 0 ; ds:si指向ds:0
mov es, ax
mov di, 16 ; es:di指向ds:10H
mov cx, 16 ; 循环16次
cld ; 设置D-Flag=0,正向传送
rep movsb
code ends
实验:将F000H段中的最后16个字符串复制到data段中。
data segment
db 16 dup (0)
data ends
code segment
mov ax, 0F000H
mov ds, ax
mov si, 0FFFFH ; ds:si指向F000:FFFF
mov ax, data
mov es, ax
mov di, 15 ; es:di指向data:000F
mov cx, 16
std ; 设置D-Flag=1,逆向传送
rep movsb
code ends
11.11 pushf和popf
pushf:将标志寄存器的值压栈。
popf:从栈中弹出数据,送入标志寄存器中。
pushf和popf为直接访问标志寄存器的一种方法。
14 端口
14.3 移位指令
14.3.1 shl和shr指令
shl和shr是逻辑移位指令。
(1)shl是逻辑左移指令
shl opr cnt ; opr: 操作数 cnt:移位数
功能为:
- 将一个寄存器或内存单元中的数据向左移位;
- 将最后移出的一位写入C-Flag中;
- 最低位用0补充。
mov al, 01001000b
shl al, 1 ; 将al中的数据左移一位,结果(al)=10010000,C-Flag=0
shl al, 1 ; 将al中的数据左移一位,结果(al)=00100000,C-Flag=1
如果移动位数大于1时,必须将移动位数放在cl中:
mov al, 01010001b
mov cl, 3
shl al, cl ; 移位后,(al)=10001000b,C-Flag=0
(2)shr是逻辑右移指令,功能和shl相反:
- 将一个寄存器或内存单元中的数据向右移位;
- 将最后移出的一位写入C-Flag中;
- 最高位用0补充。
mov al, 10000001b
shr al, 1 ; 将al中的数据右移一位,结果(al)=01000000b,CF=1
shr al, 1 ; 将al中的数据右移一位,结果(al)=00100000b,CF=0
如果移动位数大于1时,必须将移动位数放在cl中:
mov al, 01010001b
mov cl, 3
shr al, cl ; (al)=00001010b,最后移出的是0,C-Flag=0
16 直接定址表
16.1 描述了单位长度的标号
之前在代码段中使用标号标记指令、数据、段的起始地方方式:
code segment
a: db 1, 2, 3, 4, 5, 6, 7, 8
b: dw 0
start:
... ...
code ends
code、a、b、start都是标号,仅仅表示了内存单元的地址。
使用标号表示内存单元的地址和内存单元的长度:
code segment
a db 1, 2, 3, 4, 5, 6, 7, 8
b dw 0
start:
... ...
code ends
标号a:描述了从地址code:0开始,以后的内存单元都是字节单元;
标号b:描述了从地址code:8开始,以后的内存单元都是字单元。
因为这种标号包含了对单元长度的描述,所以在指令中,可以代表一个段中的内存单元。
; b代表内存单元是字单元
mov ax, b ; 相当于mov ax, cs:[8]
mov b, 2 ; 相当于mov word ptr cs:[8], 2
inc b ; 相当于inc word ptr cs:[8]
将这种标号称为数据标号,它标记了存储数据的单元的地址和长度,不同于仅仅表示地址的地址标号。
16.2 在其他段中使用数据标号
一般不将数据定义在代码段,而定义到其它段中。在其它段中,也可以使用数据标号来描述存储数据的单元的地址和长度。
注:在后面加”:“的地址标号,只能在代码段中使用。
实验:将data段中a标号处的8个数据累加,结果存储到b标号处的字中。
8assume cs:code, ds:data
data segment
a db 1, 2, 3, 4, 5, 6, 7, 8 ; 地址:code:0,以后的内存单元都是字节
b dw 0 ; 地址:code:8,以后的内存单元都是字
data ends
code segment
mov ax, data
mov ds, ax ; ds指向data段
mov si, 0
mov cx, 8
s: mov al, a[si] ; mov al, cs:0[si]
mov ah, 0
add b, ax ; 编译为add [8], ax
inc si
loop s
mov ax, 4c00h
int 21h
code ends
end
可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值:
data segment
a db 1, 2, 3, 4, 5, 6, 7, 8
b dw 0
c dw a, b ; 相当于:c dw offset a, offset b
data ends
data segment
a db 1, 2, 3, 4, 5, 6, 7, 8
b dw 0
c dd a, b ; 相当于:c dw offset a, seg a, offset b, seg b
data ends
seg操作符:取得某一个标号的段地址。
16.2 显示的原理
8086CPU显示的原理,由于后面会用到,这里提前进行说明。
原理:屏幕上的内容=显存中的数据
8086CPU显存地址空间为:A0000H~BFFFFH,总共128KB。其中B8000H~BFFFFH共32KB的空间,是80*32(25行*80列)彩色字符模式第0页的显示缓冲区。
16.3 直接定址表
实验:编写子程序,以十六进制的形式在屏幕中间显示给定的字节型数据(例如2BH)。
分析:一个字节需要两个十六进制数码来表示,子程序需要在屏幕上显示两个ASCLL字符。
-
数值0~9和字符‘0’~‘9’之间的映射关系:数值+30H=对应字符的ASCLL值。
-
数值10~15和‘A’~'F'之间的映射关系:数值+37H=对应字符的ASCLL值。
可以看出,数值0~15和字符'0'~'F'之间没有一致的映射关系存在,我们应该在它们之间建立新的映射关系。
具体做法:建立一张表,表中依次存储字符‘0’~‘F’,通过数值0~15直接查找到对应的字符。
assume cs:code
code segment
start:
mov al, 2bH
call showbyte
mov ax, 4c00h
int 21h
; 用al传送要显示的数据
showbyte:
jmp short show
table db '0123456789ABCDEF' ; 字符表
show:
push bx
push es
push cx
mov ah, al
mov cl, 4
shr ah, cl ; 右移4位,ah中得到高4位的值,(ah)=20h
and al, 00001111b ; al中为低4位的值,(al)=0bh
mov bl, ah
mov bh, 0 ; (bx)=2
mov ah, table[bx] ; 用高4位的值作为相对于table的偏移,取得对应的字符
mov bx, 0b800h ; 要改写的地址空间
mov es, bx
mov es:[160*12+40*2], ah ; es:[160*12+40*2]='2'
mov bl, al
mov bh, 0 ; (bx)=b
mov al, table[bx] ; 用低4位的值作为相对于table的偏移,取得对应的字符
mov es:[160*12+40*2+2], al ; es:[160*12+40*2]='B'
pop cx
pop es
pop bx
ret
code ends
end start
求解思路:利用表,在两个数据集合之间建立一种映射关系,用查表的方法根据给出的数据得到其在另一集合中的对应数据。
优点:算法清晰和简洁、加快运算速度、使程序易于扩充。
实验:编写一个子程序,计算sin(x),x∈P{0°, 30°, 60°, 90°, 120°, 150°, 180°},并在屏幕中间显示计算结果。
思路:将sin(x)的结果存储到一张表中,然后用角度值差表,找到对应的sin(x)的值。
assume cs:code
code segment
start:
mov al, 60
call showsin
mov ax, 4c00h
int 21h
; 用al传送要显示的数据
showsin:
jmp short show
; 字符串偏移地址表
table dw ag0 ag30 ag60 ag90 ag120 ag150 ag180 ; 字符串偏移地址表
ag0 db '0'0 ; sin(0)对应字符串
ag30 db '0.5'0 ; sin(30)对应字符串
ag60 db '0.866'0 ; sin(60)对应字符串
ag90 db '1'0 ; sin(90)对应字符串
ag120 db '0.866'0 ; sin(120)对应字符串
ag150 db '0.5'0 ; sin(150)对应字符串
ag180 db '0'0 ; sin(180)对应字符串
show:
; 寄存器压栈
push bx
push es
push si
mov bx, 0b800h
mov es, bx
; 用角度值/30作为相对于table的偏移,取得对应的字符串的偏移地址,放在bx中
mov ah, 0
mov bl, 30
div bl ; 30/30=1->table[0], 60/30=2
mov bl, al
mov bh, 0
add bx, bx ; 表的索引为dw类型
mov bx, table[bx]
; 显示sin(x)对应的字符串
mov si, 160*12+40*2
shows:
mov ah, cs:[bx]
cmp ah, 0
je showret
mov es:[si], ah
inc bx
add si, 2
jmp short shows
showret:
pop si
pop es
pop bx
ret
code ends
end start
16.4 程序入口地址的直接定址表
可以在直接定址表中存储子程序的地址,从而方便的实现不同子程序的调用。
实验:实现一个子程序setscreen,为显示输出提供如下功能:1)清屏;2)设置前景色;3)设置背景色;4)向上滚动一行。
解决方式:
- 将4个功能写成4个子程序;
- 将这些功能子程序的入口地址存储在一个表中,它们在表中的位置和功能号相对应;对应关系为:
- 功能号*2=对应的功能子程序在地址表中的偏移。
子程序入口参数说明:
- 用ah寄存器传递功能号(0:表示清屏;1:表示设置前景色;2:表示设置背景色;3:表示向上滚动一行);
- 对2、3号功能,用al传递颜色值,al∈{0, 1, 2, 3, 4, 5, 6, 7}。
; 主程序
assume cs:code
code segment
start:
; 主程序
; setscreen子程序
; 各功能实现
code ends
end start
; 实现一个子程序setscreen,为显示输出提供如下功能:
; 1)清屏;
; 2)设置前景色;
; 3)设置背景色;
; 4)向上滚动一行。
assume cs:code
code segment
start:
mov ah, 3 ; ah传递功能号
mov al, 3 ; al传送颜色值
call setscreen
mov ax, 4c00h
int 21h
setscreen:
jmp short set
table dw sub1, sub2, sub3, sub4 ; 字符串偏移地址表
set:
push bx
cmp ah, 3
ja sret ; 检查参数合法性
mov bl, ah
mov bh, 0
add bx, bx ; dw类型,占2个字节
call word ptr table[bx] ; 调用子函数
sret:
pop bx
ret
; 清屏,将显存中当前屏幕中的字符设置为空格符
sub1:
push bx
push cx
push es
mov bx, 0b800h
mov es, bx
mov bx, 0
mov cx, 2000 ; 屏幕共25行*80列
sub1s:
mov byte ptr es:[bx], ' '
add bx, 2
loop sub1s
pop es
pop cx
pop bx
ret
; 设置前景色:设置显存中奇地址的属性字节的第0、1、2位
sub2:
push bx
push cx
push es
mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000 ; 屏幕共25行*80列
sub2s:
and byte ptr es:[bx], 11111000b
or es:[bx], al ; 属性字节低3位是前景色
add bx, 2
loop sub2s
pop es
pop cx
pop bx
ret
; 设置前景色:设置显存中奇地址的属性字节的第4、5、6位
sub3:
push bx
push cx
push es
mov cl, 4
shl al, cl
mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000 ; 屏幕共25行*80列
sub3s:
and byte ptr es:[bx], 10001111b
or es:[bx], al ; 属性字节低3位是前景色
add bx, 2
loop sub3s
pop es
pop cx
pop bx
ret
; 向上滚动一行:依次将第n+1行的内容复制到第n行处,最后一行为空
sub4:
push cx
push si
push di
push es
push ds
mov si, 0b800h
mov es, si
mov ds, si
mov si, 160 ; ds:si指向第n+1行
mov di, 0 ; es:di指向第n行
cld
mov cx, 24 ; 共复制24行
; 复制前24行
sub4s:
push cx
mov cx, 160
rep movsb ; 复制
pop cx
loop sub4s
; 清空最后一行
mov cx, 80
mov si, 0
sub4s1:
mov byte ptr es:[160*24+si], ' '
add si, 2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
code ends
end start
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)