汇编语言实验 3:寻址

443 阅读9分钟

1. 预备知识

  1. 当数据存放在内存中时,我们可以用多种方式来定位该数据,这个过程称为寻址,即寻找数据所在内存单元的段地址和偏移地址。

  2. 在 8086CPU 中,只有 bx、si、di、bp 这四个寄存器可以用在中括号内进行内存单元的寻址。这四个寄存器可以单独出现,或只能以四种组合出现:[bx+si]、[bx+di]、[bp+si]、[bp+di]。

  3. 8086CPU 指令处理的数据长度包括字节和字两种,在处理数据时指定处理长度的方式:通过寄存器,如使用 AX 表示处理字数据、使用 AL 表示处理字节数据;在没有寄存器参与的内存单元的访问中,通过关键字 word 或 byte 显式指定,如 mov byte ptr [di],2h

  4. [bx+si+idata] 的寻址方式为结构化数据的处理提供了方便:bx 定位整个结构体,idata 定位结构体的某一项,si 定位项中的元素。

  5. div 是汇编语言中的除法指令。对于除数而言,有 8 位和 16 位两种。对于被除数,如果除数为 8 位则被除数为 16 位,默认存放在 AX 中;如果除数为 16 位则被除数为 32 位,DX 存放高 16 位、AX 存放低 16位。对于结果,如果除数为 8 位,则 AL 存放商、AH 存放余数;如果除数为 16 位,则 AX 存放商、DX 存放余数。

2. 实验任务 1:修改单词中的字母

将 data 段中每个单词中的前四个字母改为大写:

assume cs:code,ds:data
data segment
    db '1. display      '
    db '2. brows        '
    db '3. replace      '
    db '4. modify       '
data ends
code segment
start:
    ;待完成部分
code ends
end start

2.1 实验分析

在数据段中,字符串后面部分使用空格填充至 16 字节,其在内存中以如下方式组织:

基于 [bx+si+idata] 的寻址方式,使用 bx 定位每个字符串、si 定位每个字母、在处理字符串时 idata 定位从第 4 个字符开始处理。包含两个变量,使用双重循环,外层循环用于遍历四个单词,内层循环用于遍历四个字母。

由于还没有介绍比较指令的使用,这里使用位运算将小写字母转换为大写字母。整体代码为:

assume cs:code,ds:data
data segment
	db '1. display      '
	db '2. brows        '
	db '3. replace      '
	db '4. modify       '
data ends
code segment
start:
	mov ax,data
	mov ds,ax		;使用段寄存器DS指向数据段data
	mov bx,0			
	mov cx,4		;外层循环次数
s1:	
	push cx			;保护外层循环CX的值
	mov si,0			
	mov cx,4		;内层循环次数
s2:	
	mov al,[bx+si+3]
	and al,11011111b	;按位与运算将小写字母转换为大写字母
	mov [bx+si+3],al
	inc si			;内层循环每次偏移1个字节处理字母
	loop s2
	add bx,16		;外层循环每次偏移16个字节处理单词
	pop cx			;恢复外层循环CX的值
	loop s1
	mov ax,4c00h
	int 21h
code ends
end start

2.2 实验结果

程序运行前,数据段在内存中的形式:

编译,链接并运行可执行文件。程序运行后,数据段在内存中的形式:

3. 实验任务 2:结构化数据的访问

下面是某公司从 1795 年到 1995 年的基本情况:

下面程序已完成数据定义:

assume cs:codeseg
data segment
	db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
	db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
	db '1993','1994','1995'
	;存放年份,每一项用4个字节表示
	dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
	dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
	;存放收入,每一项用4个字节表示
	dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
	dw 11542,14430,15257,17800
	;存放雇员,每一项用2个字节表示
data ends
table segment	
	db 21 dup ('year summ ne ?? ')
	;表格段,年份(4)用year初始化、收入(4)用summ初始化、雇员(2)用ne初始化、人均收入(2)用??初始化
table ends

编程,将 data 段中的数据按如下格式写入 table 段中,并计算每年的人均收入并保存到 table 段。

3.1 实验分析

采取每一项单独分析,最后合并的思路。首先往 table 段写入表示年份的字符串,每一项用四个字节表示。考虑每次写入一个字,使用两个连续的 mov 指令即可。该部分代码为:

mov ax,ds:[si]
mov es:[bp],ax     ;写入前两个字节
mov ax,ds:[si+2] 
mov es:[bp+2],ax   ;写入后两个字节

收入字段使用四个字节表示,采用和年份字段相似的处理。在 data 段中,收入部分的数据相对于年份的偏移为 42×4=168 字节;在 table 段,收入部分的数据相对于年份的偏移为 5 字节。该部分代码为:

mov ax,ds:[84+si]
mov es:[bp+5],ax     ;写入前两个字节
mov ax,ds:[84+si+2] 
mov es:[bp+5+2],ax   ;写入后两个字节

雇员字段使用两个字节表示,使用一个 mov 语句即可。雇员部分的数据相对于年份的偏移为 21×4=84 字节;在 table 段,雇员部分的数据相对于年份的偏移为 10 字节。该部分代码为:

mov ax,ds:[168+si] 
mov es:[bp+10],ax

最后,对于人均收入字段,由收入字段和雇员字段做除法得到。由于除数雇员字段为 16 位,所以被除数为 32 位,且 DX 存放高 16 位、AX 存放低 16 位。人均收入的数据相对于年份的偏移为 13。该部分代码为:

mov ax,ds:[84+si]	    ;AX存放低16位
mov dx,ds:[84+si+2]	    ;DX存放高16位
div word ptr ds:[168+di]    ;word ptr指定除法运算的为16位,且结果存放在AX中(整除)
mov es:[bp+13],ax	    ;写入

上述代码中,si 用于辅助从 data 段取四字节的数据,每次偏移量为 4 字节;di 用于辅助从 data 段取两字节的数据,每次偏移量为 2 字节;bp 用于索引表格中的每一行数据,每次偏移量为 16 字节。最后,在外层加上循环量为 21 的循环,整体代码为:

assume cs:codeseg
data segment
    db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
    db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
    db '1993','1994','1995'
    ;存放年份,每一项用4个字节表示
    dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
    dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
    ;存放收入,每一项用4个字节表示
    dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
    dw 11542,14430,15257,17800
    ;存放雇员,每一项用2个字节表示
data ends
table segment	
    db 21 dup ('year summ ne ?? ')
    ;表格段,年份(4)用year初始化、收入(4)用summ初始化、雇员(2)用ne初始化、人均收入(2)用??初始化
table ends

codeseg segment
start:
    mov ax,data
    mov ds,ax			;段寄存器DS指向段data
    mov ax,table	
    mov es,ax 			;段寄存器ES指向段table

    mov si,0
    mov di,0
    mov bp,0
    mov cx,21
s:	
    mov ax,ds:[si]
    mov es:[bp],ax 		;先移动前两个字节
    mov ax,ds:[si+2]
    mov es:[bp+2],ax		;再移动后两个字节

    mov ax,ds:[84+si]		;84是数据段中存放收入内存相对于存放年份的偏移	
    mov es:[bp+5],ax 		;5是表格段中存放收入内存相对于存放年份的偏移,先移动前两个字节
    mov ax,ds:[84+si+2]
    mov es:[bp+5+2],ax		;再移动后两个字节

    mov ax,ds:[168+di]		;168是数据段中存放收入内存相对于存放年份的偏移	
    mov es:[bp+10],ax		;10是表格段中存放收入内存相对于存放年份的偏移,每次处理两个字节

    mov ax,ds:[84+si]		;AX存放低16位
    mov dx,ds:[84+si+2]		;DX存放高16位
    div word ptr ds:[168+di]    ;word ptr指定除法运算为16位,且结果存放在AX中(整除)
    mov es:[bp+13],ax		;写入

    add si,4			;每次偏移4个字节从data段中取4字节数据
    add di,2			;每次偏移2个字节从data段中取2字节数据
    add bp,16			;每次偏移16个字节写入下一行数据
    loop s

    mov ax,4c00h
    int 21h
codeseg ends
end start

3.2 实验结果

程序运行前,table 段的内容为:

程序运行后,table 段的内容为:

以白色部分第 9 行数据为例,31 39 38 33 分别为 1983 的 ASCII 码值。蓝色部分,C391 的十进制表示为 50065;绿色部分,01DC 的十进制表示为 476;计算得到人均收入为 105,对应的十六进制为 69,即黄色部分。

4. 总结

  1. 寻址即定位数据在内存中的位置,本文主要介绍了寻址在结构化数据中的应用

  2. div 是汇编语言的除法指令,有 8 位除法和 16 位除法两种。后续还将介绍除法溢出等相关内容

  3. 参考:汇编语言/王爽著.——北京:清华大学出版社,2003