汇编语言阶段一总结

将数据代码放入不同的段

汇编语言程序可以将数据,栈和代码都放到一个段里面,但是也可以将程序,栈和代码分别放到不同的段里,下图就是定义多个段的程序

assume cs:codesg,ds:data,ss:stack

data segment
	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends

stack segment
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends

codesg segment
start:mov ax,stack
	  mov ss,ax
	  mov sp,20h   ;设置栈顶指向stack:20
	  
	  mov ax,data
	  mov ds,ax    ;设置ds指向data段
	  
	  mov bx,0
	  mov cx,8
	s:push [bx]
	  add bx,2
	  loop s
	  
	  mov bx,0
	  mov cx,8
   s0:pop [bx]
      add bx,2
	  loop s0

	  mov ax,4c00h
      int 21h	  
codesg ends

end start

上述程序定义了三个段,分别是:

  • 存放数据的数据段
  • 存放堆栈数据的堆栈段
  • 存放程序的代码段

上图所示的代码中有一个需要注意的地方是堆栈指针的取值,也就是

mov sp,20h

要将明白sp的取值,就不得不明白数据在内存中的存储形式,下图示字在内存中的存储,内存中存储的数据分别是:4E20H和0012H。这里要明白字和字节的换算关系:

1字节 = 8位 (1byte = 8bite)
1字 = 2字节 = 16位 (1word = 2byte = 16bite)

在这里插入图片描述
由上图可以知道,内存中存储4E20H这个数据时,是把4E20这个数据拆分成了两个数据进行分别存储,4E和20,并将高位存储在高地址中,把低位存储在低地址中。
回过头来,再来分析SP的值,我们可以知道在初始化时Stack中存放了如下所示的数据:

	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

上述数据是字型的,也就是一个数据有2个字节,因此,栈中所有的数据所占的存储空间是16*2 = 32个字节,而32是10进制数,转换为16进制也就是20H,,因此堆栈指针的初始值应该设置为20H。

and 和 or指令

为了更好的解释and和or指令,这里通过大小写字符转换的问题为例子来说明这两个指令的作用。

大小写字母的关系

要实现大小写字符的转换,那就需要知道大小写字符之间的关系是什么。我们分别列出大小写字符的ASCII码,如下表所示:

大写 十六进制 二进制 小写 十六进制 二进制
A 41 01000001 a 61 01100001
B 42 01000010 b 62 01100010
C 43 01000011 c 63 01100011

通过对比,我们可以发现。小写字母的ASCII码比都比大写字母的ASCII码要大20H,这反映在二进制上,也就是大写字母的第六位总是0,小写字母的第六位总是1,因此,要实现大小写字母的转换,只需要将小写字母的第六位变为0即可。
下面代码关于and和or的例子:

assume cs:codesg,ds:datasg

datasg segment
	db 'BaSiC'        ;将这里的大写字母转换为小写字母
	db 'iNfOrMaTiOn'  ;将这里的小写字母转换为大写字母
datasg ends

codesg segment
start:mov ax,datasg
	  mov ds,ax
	  
	  mov bx,0
	  mov cx,5
	s:mov al,[bx]
	  or al,00100000b
	  mov [bx],al
	  inc bx
	  loop s
	  
	   mov bx,5
	   mov cx,11
	s1:mov al,[bx]
	   and al,11011111b
	   mov [bx],al
	   inc bx
	   loop s1
	   
	   mov ax,4c00h
	   int 21h
codesg ends

end start

用[bx+idata]的方式进行数组的处理

上述程序我们用[bx]的方式来指明一个内存单元,还可以用一种更加灵活的方式来指明内存单元:[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata。下面举一个例子说明这个语句的用法:

mov ax,[bx+200]

上述语句的含义是:将一个内存单元的内容送入ax,这个内存单元的长度为2个字节(字单元),存放一个字。偏移地址为bx中的数值加上200,段地址在ds中。
数学描述如下:

(ax) = ((ds)*16+(bx)+200)

下图所示代码是用[bx+idata]的方式进行数组的处理

assume cs:codeseg,ds:dataseg

dataseg segment
	db 'BaSiC'  ;大写变小写 0->1
	db 'MinIX'  ;小写变大写 1->0
dataseg ends

codeseg segment
start:mov ax,dataseg
      mov ds,ax
	  
	  mov bx,0
	  mov cx,5
	s:mov al,[bx]
	 ;mov al,0[bx]
	  or al,00100000b
	  mov [bx],al
	 ;mov 0[bx],al
	  mov al,[bx+5]
	 ;mov al,5[bx]
	  and al,11011111b
	  mov [bx+5],al
	 ;mov 5[bx],al
	  inc bx
	  loop s
	  
	  mov ax,4c00h
	  int 21h
codeseg ends

end start

添加了[bx+idata]的方式,实现了类似于C原因数组的效果,其中注释掉的代码是作为[bx+idata]的另一种表达形式。也是可以进行运行的。

SI 和 DI

si和di是8086CPU中和bx功能相近的寄存器,需要注意的是si和di不能够分成两个8位寄存器来使用。下面的三组指令实现的同样的功能

(1)mov bx,0
   mov ax,[bx]
(2)mov si,0
   mov ax,[si]
(3)mov di,0
   mov ax,[di]

下面的代码利用si和di将字符串复制到它后面的区域中去:

assume cs:codeseg,ds:datasg

datasg segment
	db 'welcome to masm!'
	db '................'
datasg ends

codeseg segment
start:mov ax,datasg
      mov ds,ax
	  
	  mov si,0
	  mov cx,8
	s:mov ax,[si]
	 ;mov ax,0[si]
	  mov [si+16],ax
	 ;mov 16[si],ax
	  add si,2
	  loop s
	  
	  mov ax,4c00h
	  int 21h
codeseg ends

end start

上述代码中运用来了si实现了同bx一样的功能,但是与bx不同的一点是,si只实现以字为单位的运算,因此,虽要复制得字符串有16个字节,但是loop循环的次数却只有8次。

[bx+si]和[bx+di]

在上述的代码中,我们使用了形如[bx(si或di)]和[bx(si或di)+idata]的方式来进行寻址,这里介绍一种更为灵活的寻址方式:[bx+si]、[bx+di]
现有如下指令:

mov ax,[bx+si]

上述指令的含义是:将一个内存单元送入ax,这个内存单元的长度是2字节(字单元),存放一个字,偏移地址为bx中数值加上si中的数值,段地址在ds中。数学化表示为:

(ax) = ((ds)*16+(bx)+(si))

该指令也可以写成如下常用的形式:

mov ax,[bx][si]

下面为[bx+si指令的简单运用:

mov ax,2000h
mov ds,ax
mov bx,1000h
mov si,0
mov ax,[bx][si]
inc si
mov cx,[bx][si]
inc si
mov di,si
add cx,[bx][di]

上述代码的主要作用就是分别访问了2000:1000、2000:1001、2000:1002的地址的数据,分别将其赋值给ax,cx。

[bx+si+idata]和[bx+di+idata]

[bx+si+idata]表示一个内存单元,它的偏移地址为(bx)+si+idata。
现有人如下指令

mov ax,[bx+si+idata]

用数学表达式描述为:

(ax)=((ds)*16+(bx)+(si)+idata)

该指令通常也可以写成如下的形式:

mov ax,[bx][si].200

下面试运用[bx+si+idata]处理问题的相关程序

assume cs:codesg,ss:stacksg,ds:datasg

stacksg segment
  dw 0,0,0,0,0,0,0,0
stacksg ends

datasg segment
  db '1. display      '
  db '2. brows        '
  db '3. replace      '
  db '4. modify       '
datasg ends

codesg segment

	start:mov ax,stacksg
	      mov ss,ax
		  mov sp,16
		  mov ax,datasg
		  mov ds,ax
		  mov bx,0
		  
		  mov cx,4
		  
	   s0:push cx
	      mov si,0
		  mov cx,4
		  
		s:mov ax,[bx][si].3
		 ;mov ax,[bx+3+si]
		  and al,11011111b
		 ;mov [bx+3+si],ax
		  mov [bx][si].3,ax
		  inc si
		  loop s
		  
		  add bx,16
		  pop cx
		  loop s0
		  
		  mov ax,4c00h
		  int 21h
		  
codesg ends

end start

上述代码的作用是将dataseg段的每个单词前的四个字母改为大写字母,程序的实现思路是采用了如下的几种方法

  • 循环嵌套的方法。
    • 第一层循环用于控制对哪一个单词进行寻址,用于每一个单词的字节大小均为16,因此在操作完第一个单词后,只需要将bx加16就可定位到下一个单词。
    • 第二层循环用于控制对单词的各个字母进行寻址,从而能够改变到每一个单词的各个字母。
  • 堆栈保存循环次数
    • cx值压栈。由于控制汇编语言进行循环次数的寄存器均采用的是cx寄存器,因此,在第二层循环执行前必须将cx的值保存下来,这里采用堆栈的机制保存cx的值,在第二层循环执行前将cx的值进行压栈。
      • cx值出栈。 然后为了能够使第一层循环正常运行,在第二层循环执行完一次后,需要将堆栈里的值弹出,赋值给cx,从而保证了第一层循环的正确运行。

指令要处理的数据长度

8086CPU中可以处理两种长度的数据,因此,在机器指令中要指明处理的数据长度是多长的。在汇编语言中,主要存在如下几种方法来指明数据的长度:

  • 通过寄存器名指明要处理的数据的尺寸
    • 字操作:mov ax,1 、mov ds,ax
    • 字节操作:mov al,1、mov al,bl
  • 在没有寄存器名存在的情况下,用操作符X pyr指明内存单元的长度
    • 字操作:mov word ptr ds:[0],1、inc word ptr [bx]
    • 字节操作:mov byte ptrr ds:[0],1、inc byte ptr ds:[0]

div指令

div是除法指令,使用div的时候应该注意如下的问题:

  • 除数:有8位和16位两种,在一个reg或内存单元中。
  • 被除数:默认放在AX或DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16位,被除数则为32位,在DX和AX中存放,DX中存放高16位,AX存放低16位
  • 结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。

具体操作例子:

div byte ptr ds:[0]

含义:
(al) = (ax)/((ds)*16+0)的商
(ah)=(ax)/((ds)*16+0)的余数

div word ptr es:[0]

含义:
(ax)= [(dx)*10000H+(ax)]/((es)*16+0)的商
(ax)= [(dx)*10000H+(ax)]/((es)*16+0)的余数

伪指令dd,db,dw,dup

  • db:字节型数据
  • dw:字型数据
  • dd:定义双字型数据

比如如下例子:

data segment
	dd 100001
	dw 100
	dw 0
data ends
  • dup:dup是一个操作符,在汇编语言中同db、dw、dd等一样,也是由编译器识别处理的符号。

比如如下例子:

db 3 dup (0)

相当于 db 0,0,0

db 3 dup (0,1,2)

相当于 db 0,1,2,0,1,2,0,1,2
使用dd能够使得程序变得更加简洁,简短。

综合例子

寻址方式在结构化数据访问中的应用

详细的程序代码:

assume cs:codeseg,ds:dataseg,es:tableseg

dataseg 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'
	;以上是表示21年的21个字符
	
	dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
	dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
	;以上是表示21年公司总收入的21个dword型数据
	
	dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
	dw 11542,14430,15257,17800
	;以上表示21年公司雇员人数的21个word型数据
dataseg ends

tableseg segment
	db 21 dup ('year summ ne ?? ')
tableseg ends

codeseg segment
start:mov ax,dataseg
      mov ds,ax
	  
	  mov ax,tableseg
	  mov es,ax
	  
	  mov bx,0
	  mov si,0
	  mov di,0
	  
	  mov cx,21
	s:mov ax,[bx]
	  mov es:[si],ax   ;先将年份的低位存入
	  mov ax,[bx+2]
	  mov es:[si+2],ax ;后将年份的高位存入
	  
	  mov ax,[bx+84]
	  mov es:[si+5],ax ;先将总收入的低位存入
	  mov ax,[bx+84+2]
	  mov es:[si+7],ax ;后将总收入的高位存入
	  
	  mov ax,[di+168]
	  mov es:[si+10],ax ;将雇员人数的低位存入
	  
	  mov ax,[bx+84]    ;将总收入的低位存入ax
	  mov dx,[bx+84+2]  ;将总收入的高位存入dx
	  div word ptr ds:[di+168]
	  mov es:[si+13],ax
	  
	  add si,16
	  add bx,4
	  add di,2
	  
	  loop s
	  
	  mov ax,4c00h
	  int 21h
codeseg ends

end start

下图是代码运行结束后内存中各个数据的存储位置

由图中可以看出:

  • (1)号标号所框选的内容就是具体的年份存储的形式,因为年份在存储的时候采用的是字符存储的方式,每一个数字占一个字节,一共占了4个字节。另外,字符在计算机内存中存储时采用的是以ASCII码的形式进行存储,图中的每一个数字代码一个字节。
  • (2)号标号中所框选的内容就是年份存储年份后的一个空格,空格在内存中的存储值是20H。因此,对于table列表中的其他空格存储的值都是20H。图中的第四列,第六列,第8列也都是存储的空格。
  • (3)号标号中存储的是总的收入,因为总的收入需要占用4个字节,因此,也就需要四列来存储所有的收入指。其中,在进行读取的时候,要遵循高位数据存放在高位内存中,低位数据存放在低位内存中。例如,在读取最后一个数据时,正确的顺序应该是00 5A 97 68
  • (5)号标号存储的内容是公司的雇员数,雇员数占2个字节,也就是图中占两列
  • (7)号标号存储的内容是公司员工的人均收入,占两个字节。
发布了19 篇原创文章 · 获赞 6 · 访问量 1725

猜你喜欢

转载自blog.csdn.net/weixin_42616791/article/details/103638776