《汇编语言》学习笔记-3

注:本文档为“《汇编语言(第3版) 》王爽著”阅读过程中记的笔记。

参考视频:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili

11 标志寄存器

CPU内部的标记寄存器具有以下作用:

  1. 用来存储相关指令的某些执行结果;
  2. 用来为CPU执行相关指令提供行为依据;
  3. 用来控制CPU的相关工作方式。

8086CPU的标志寄存器是16位的,其中存储的信息通常被称为程序状态字(PSW)。

flag寄存器的按位起作用的,每一个位有专门的含义,记录特定的信息。

image-20220906221636292

其中,第1、3、5、12、13、14、15位没有使用。

debug.exe的的标志寄存器:

image-20220908084452170

直接访问标志寄存器的方法:

  • 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

image-20220906230914912

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

image-20220906232120990

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,如果指令进行的是有符号数运算,则结果非负。

某些指令将影响标志寄存器中的多个标志位,这些被影响的标记位比较全面的记录了指令的执行结果,为相关的处理提供了所需的依据。

image-20220906234243006

debug.exe中:ng表示负数,pl表示正数。

11.4 C-Flag(Carry Flag)标志

flag的第0位是CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高位有效位向更高位的进位值,或从更高位的借位值。

CF记录指令执行后,

  • 有进位或借位,C-Flag=1;
  • 无进位或借位,C-Flag=0。

对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相当于最高有效位的更高位。

image-20220907213840060

mov al, 98H
add al, al	; 执行后,(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值

add al, al	; 执行后,(al)=60H,CF=0,CF记录了从最高有效位向更高位的进位值

image-20220907214545079

当两个数据做减法的时候,有可能向更高位借位。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位)中。

思路:

  1. 先将低16位相加,完成后,C-Flag中记录本次相加的进位值;
  2. 再将次高16位和C-Flag(来自低16位的进位值)相加,完成后,C-Flag中记录本次相加的进位值;
  3. 最后高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位的数据相加

image-20220911143756288

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指令相当于进行下面几步操作:

  1. ((es)*16+(di)*16)=((ds)*16+(si))

  2. 如果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为直接访问标志寄存器的一种方法。

image-20220911163123366

14 端口

14.3 移位指令

14.3.1 shl和shr指令

shl和shr是逻辑移位指令。

(1)shl是逻辑左移指令

shl opr cnt	; opr: 操作数 cnt:移位数

功能为:

  1. 将一个寄存器或内存单元中的数据向左移位;
  2. 将最后移出的一位写入C-Flag中;
  3. 最低位用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相反:

  1. 将一个寄存器或内存单元中的数据向右移位;
  2. 将最后移出的一位写入C-Flag中;
  3. 最高位用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

image-20220911201115939

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页的显示缓冲区。

页-2

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=对应的功能子程序在地址表中的偏移。

子程序入口参数说明:

  1. 用ah寄存器传递功能号(0:表示清屏;1:表示设置前景色;2:表示设置背景色;3:表示向上滚动一行);
  2. 对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
posted @   zhengcixi  阅读(385)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
回到顶部
点击右上角即可分享
微信分享提示

目录导航