基础汇编(二)

如何定义自己的数据?
dw 1,2,3.4
dw的意义是define word。就是定义自己的字型数据

计算八个字型数据 1,2,3,4,5,6,7,8并保存在ax中

assume cs:code
;阅读性 初始化 数据从哪里来 数据放到哪里去

;cpu 将 cs:ip 所指向的信息当做指令
;cpu 将 ss:sp 所指向的地址当做栈顶
;cx寄存器,loop指令每执行一次,cx中的值会减一

;问题:计算下面八个字型数据,并将结果保存在ax寄存器中
code segment

			
		

			dw 1,2,3,4,5,6,7,8 ;dw是define word的意思,就是定义使得后面的数据都是字型数据
			
start:		mov bx,0 ;偏移地址寄存器
			mov ax,0
			mov cx,8 ;每执行一次loop指令,cx中的值会减一
			
adNumber:	
			add ax,cs:[bx];将结果存放在ax中
			add bx,2;每次是一个字即两个字节,所以偏移地址每次得加2
			loop adNumber
		
			mov AX,4CH;程序结束后返回到dos命令状态,如果没有这指令则无法返回dos
			int 21H

code ends
end start

如果不利用start,可以怎么修改,可以在start对应位置换一个代号,然后在最前面利用 jmp 代号就可。


我们以前是如何使用栈的? 答案;cpu 将 ss:sp 所指向的地址当做栈顶

如何定义自己的栈?

exe文件进行debug的时候,系统给他分配内存。

assume cs:code
;阅读性 初始化 数据从哪里来 数据放到哪里去

;cpu 将 cs:ip 所指向的信息当做指令
;cpu 将 ss:sp 所指向的地址当做栈顶
;cx寄存器,loop指令每执行一次,cx中的值会减1
;bx,一般用作偏移地址寄存器

;问题:完成程序的定义,利用栈(通过系统分配的内存)
code segment

			dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0987H,0ABDH
			
			dw 0,0,0,0,0,0,0,0
			dw 0,0,0,0,0,0,0,0 ;将这三十个二字节当做我们自己的栈空间
			
start: 		mov ax,cs 
			mov ss,ax
			mov sp,48
			
			mov bx,0
			mov cx,8
			
pushData:	push cs:[bx]
			add bx,2
			loop pushData
			
			mov bx,0
			mov cx,8
			
popData: 	pop cs:[bx]
			add bx,2
			loop popData
			
			
			mov ax,4C00H
			int 21H
			

code ends
end start

编程练习:

assume cs:code
;阅读性 初始化 数据从哪里来 数据放到哪里去

;cpu 将 cs:ip 所指向的信息当做指令
;cpu 将 ss:sp 所指向的地址当做栈顶
;cx寄存器,loop指令每执行一次,cx中的值会减1
;bx,一般用作偏移地址寄存器

;问题:下面的程序实现依次用内存 0:0 -0:15 单元中的内容改写程序中的数据,完成程序

code segment

			dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0987H,0ABDH
			
			
start:		mov ax,0
			mov ds,ax 
			mov bx,0  ;数据从哪里来  
			
			mov cx,8 ;字型数据,循环8次就足够了
			
change:		mov ax,ds:[bx];
			mov cs:[bx],ax ;这个需要把这一个指令先不看,先编译一下才能看出来
			add bx,2
			loop change

code ends
end start

超级重要的一节:P104
在这里插入图片描述
通过观察下面这张图,我们可以发现ip寄存器不是从0开始的,ip=30(十进制的48,占用了48个自己)。因为前面我们定义的数据占据了内存,占用了 ip 寄存器的变化范围,间接减少了指令的数量。


*如果段中的数据占据N个字节,那么程序加载后,该段实际占有空间为:
1.如果 N%16==0,那么占据 N个字节
2.(N/16+1)16,所占空间字节数目一定是16的倍数,一个segment最小占用的字节数是16个字节(16,32,48……)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


1.dw(即define word来定义一个字型数据)
2.db(即define byte来定义一个字节型数据)

;问题,将a段和b段中的数据相加,结果存到c段中
assume cs:code					;伪指令,告诉编译器,这里怎么翻译,那里怎么翻译
								;start 伪指令,将程序的入口记录在exe文件的可描述信息中
a segment
		db 1,2,3,4,5,6,7,8
a ends

b segment
		db 1,2,3,4,5,6,7,8
b ends

c segment
		db 1,2,3,4,5,6,7,8
c ends

code segment

	start:	

			mov ax,a  ;数据从哪里来
			mov ds,ax
			
			mov ax,c
			mov es,ax
			
			mov bx,0
			mov cx,8;loop指令每执行一次,cx中的值会减1
			

addNumber:	
			push ds
			mov dx,0
			mov dl,ds:[bx]

			mov ax,b
			mov ds,ax
			
			add dl,ds:[bx]
			mov es:[bx],dl
			inc bx
			pop ds
			loop addNumber
		

		
			mov ax,4C00H
			int 21H

code ends

end start

总结:
如何在系统分配给我们的内存中
安排自己的数据
安排自己的栈
安排自己的代码

;阅读性 初始化 数据从哪里来 数据放到哪里去
;cpu 将 cs:ip 所指向的信息当做指令
;cpu 将 ss:sp 所指向的地址当做栈顶
;cx寄存器,loop指令每执行一次,cx中的值会减1
;bx,一般用作偏移地址寄存器

;问题,将a段和b段中的数据相加,结果存到c段中
assume cs:code,ds:data,ss:stack			;伪指令,告诉编译器,这里怎么翻译,那里怎么翻译
										;start 伪指令,将程序的入口记录在exe文件的可描述信息中
data segment
				db 1,2,3,4,5,6,7,8
				;编译的规则,会影响内存的分配,会自动分配16的倍数个字节,1632,64……
				;实际占用的字节为N,如果N%16==0,那么就占N个,如果N%16!=0,那么就是(N/16+1)*16
data ends

stack segment
				dw 1,2,3,4,5,6,7,8
				dw 1,2,3,4,5,6,7,8
stack ends

code segment

start:	

			mov ax,stack ;栈的位置,为了编程的时候,逻辑更加清晰
			mov ss,ax
			
			mov ax,data ;数据的位置
			mov ds,ax
			
			
			mov bx,0
			mov ax,ds:[bx];把数据取出来给ax寄存器
			

		
			mov ax,4C00H
			int 21H

code ends

end start

前面讲到,内中中的分配的字节大小一定是16个字节的或者是16的倍数,而这边的text文件,即是4kb为一个段的,ceil(实际内存)*4KB.
[操作系统的段页式管理]
在这里插入图片描述


and运算符:按位与,逻辑运算,按照二进制位运算,and(按位与),就是将对应的位置设置为0,其他位置不变。如果要得到对应位置的数值,只需要将原来的数字去与上一个数(这个数在你想要得到的数位对应位置就放置1,其他的位置放置为0就可以得到你想要的位置)。

assume cs:code,ss:stack


stack segment
			dw 1,2,3,4,5,6,7,8
stack ends





code segment

start:	
		
		mov bx,stack
		mov ss,bx
		mov sp,128
		
		;逻辑运算,按照二进制位运算,and(按位与)
		
		mov ax,0
		
		mov al,00001111B
		and al,11110000B

			mov ax,4C00H
			int 21H

code ends

end start

在这里插入图片描述
or,或运算。按位或,对应位置只要有一个为1,结果就是1.(可以任意的将你想要设置为1的位置设置为1)

code segment

start:	
		
		mov bx,stack
		mov ss,bx
		mov sp,128
		;逻辑运算,按照二进制位运算,and(按位与),就是将对应的位置设置为0,其他位置不变
		mov ax,0
		
		and al,00001111B
		or  al,11110000B

		;或运算,逻辑运算,对应位置,只要有一个为1,结果就是1

			mov ax,4C00H
			int 21H
code ends
end start

加粗样式


以字符形式给出的数据
ASSII码,是一种字符。

ASCII码:是一种编码
信源 编码 信道(噪声) 译码 信宿

定义字符型数据,db ‘这里面填对应的字符’;
ASCII码会被显示在dobox的输出屏幕最右边。
ASCII码分为两种:
1.一种是控制字符(在dobox显示界面里面只是一个小点,无法显示原来的字符)
2.一种是打印字符(在dobox显示界面可以完整显示出对应字符)
在这里插入图片描述

data segment
			db '12345abcdefg!!!!' ;单引号
data ends

完整代码:

assume cs:code,ss:stack


data segment
			db '12345abcdefg!!!!' ;单引号
data ends

stack segment
			db 48,49,50,51,68,67,70
stack ends

code segment

start:	
		
		mov bx,stack
		mov ss,bx
		mov sp,128
		
		;逻辑运算,按照二进制位运算,and(按位与),就是将对应的位置设置为0,其他位置不变
		
		mov ax,0
		
		mov bx,data;把定义的数据给到ds寄存器这里
		mov ds,bx
		
		
			mov ax,4C00H
			int 21H

code ends

end start

在这里插入图片描述


字母字符的大小写之间的转化(原始字符:abcdefg)
代码:

assume cs:code,ss:stack


data segment
			db 'abcdefg' ;单引号
data ends

stack segment
			db 48,49,50,51,68,67,70
stack ends





code segment

start:	
		
		mov bx,stack
		mov ss,bx
		mov sp,128
		
		
		
		mov bx,data		;把定义的数据给到ds寄存器这里
		mov ds,bx
		
		mov bx,0
		mov cx,7 ;设置loop指令循环七次
	
		
								;'a'的ASCII编码为 97---> 61H--->0110 0001B
																
								;'A'的ASCII编码为 65---> 41H--->0100 0001B
								;因而,只需要两个之间进行一个按位与运算,就可以将小写字符变为大写字符,1101 1111(进行与操作)
								;如果大写字符转小写,也是进行按位与运算,就可以将大写字符变为小写字符, 0010 0000(进行或操作)
		
		
upperchar:  
		
			mov al,ds:[bx] 		;取出来
			and al,11011111B 	;进行与运算(把二进制第三位数字置为0)
			mov ds:[bx],al		;把数据放回原来的位置
			inc bx				;偏移地址自增
			loop upperchar 		;循环
		
		
			
			mov bx,0
			mov cx,7 ;设置loop指令循环七次
			
lowerchar:
			
			mov al,ds:[bx]		;取出来
			or al,00100000B;	;进行或运算(把二进制第三位数字置为1)
			mov ds:[bx],al
			inc bx
			loop lowerchar
			
		
			mov ax,4C00H
			int 21H

code ends

end start

实验结果:(最右边部分就是字母的变化情况)
(1)小写转大写:
在这里插入图片描述
(2)大写转小写:
在这里插入图片描述
结论:
1.小写字符转大写字符,将它与 1101 1111 B 进行与操作即可;
2.大写字符转小写字符,将它与 0010 0000B 进行或操作即可。

为什么要将ASCII编码从65算成A,从97算成A呢?
答案:有一个更快的运算方法,只要进行逻辑运算就可以非常迅速的完成,使用这种编码格式,就可以使得运算更加合理且迅速。


目的:为了掌握偏移地址不止可以有寄存器,还可以在寄存器之后加上数值
编程练习:
;内存中的数据排列如下2000:1000 BE 00 06 00 00 (目前数据的状态),计算对应位置的寄存器的数值

这里要利用-e把 2000:1000 对应地址的值修改为 BE 00 06 00 00
代码:

assume cs:code,ss:stack
data segment
			db 'abcdefg' ;单引号
data ends
stack segment
			db 48,49,50,51,68,67,70
stack ends
code segment
;内存中的数据排列如下2000:1000 BE 00 06 00 00 (目前数据的状态);计算对应位置的寄存器的数值
start:	
		mov ax,2000H
		mov ds,ax	
		mov bx,1000H		;设置偏移地址
		mov ax,ds:[bx]		;ax=00BEH
		mov cx,ds:[bx+1]	;cx=0600H
		add cx,ds:[bx+2]	;cx=0606H
			mov ax,4C00H
			int 21H

code ends

end start

在这里插入图片描述


利用 si, di 两个偏移地址寄存器;来进行复制
;用si和di寄存器 是想将字符串’welcome to masm!’
;复制到下面的位置’…'中
代码:

assume cs:code,ss:stack

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

stack segment
			
stack ends


code segment

start:	
					mov ax,data
					mov ds,ax
		
					mov si,0
					mov di,16
		
			
					mov ax,8 ;设置它循环复制8,一次复制两个字节,就只要8次了
					mov cx,ax 
		
copystring:			mov ax,ds:[si]
					mov ds:[di],ax
					add si,2
					add di,2
					loop copystring

					mov ax,4C00H
					int 21H

code ends

end start

在这里插入图片描述
在这里插入图片描述
结果:可以看到,字符被成功复制进去了。
下面这种是利用栈的(对一个字符进行入栈和出栈),利用一个偏移地址寄存器把他们送到指定的位置即可。
在这里插入图片描述


题目:反转一个字符串
思路:利用偏移地址寄存器进行复制和移动,注意不能使用push 和pop指令。因为他们操作的都是字型数据。只能使用字节型数据来移动
代码:

assume cs:code,ss:stack

data segment

			db   'welcome to masm!'
			db   '................'
data ends

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

code segment

start:	
					mov ax,stack
					mov ss,ax		;设置栈的段地址
					mov sp,32   	;设置栈的偏移地址[ss:sp]共同形成栈顶
					
					mov ax,data
					mov ds,ax
		
					mov si,0
					mov di,31
		
			
					mov ax,16 		;设置它循环复制16,一次复制一个字节
					mov cx,ax 
		
copystring:
					mov al,ds:[si]
					mov ds:[di],al
					inc si
					sub di,1
					loop copystring

					mov ax,4C00H
					int 21H

code ends

end start

编程:
把每个单词的首字母都变为大写字母:

;把每个单词首字母都变为大写字母
assume cs:code,ss:stack

data segment
			   ;0123456789ABCDEF
			db '1. file         '
			db '2. edit         '
			db '3. seach        '
			db '4. view         '
data ends

stack segment
			
stack ends
			

code segment

start:	
					mov ax,data
					mov ds,ax
					
					mov bx,0
					mov si,3
					mov cx,4	;4个单词执行四次就好了
					
upperchar:
					mov al,ds:[bx+si]	;取出对应的字符
					and al,11011111B	;变为大写字母
					mov ds:[bx+si],al	;覆盖原来的字符	
					add bx,16 ;每个地址之间查了16个字节
					loop upperchar

					mov ax,4C00H
					int 21H
code ends

end start

编程练习:
把下图四个单词的字母都变为大写字母,用到两重循环【中间必须要有一个临时的寄存器把cx的指保存起来才不会被覆盖掉】

;0123456789ABCDEF
'fil             '
'edi             '
'sea             '
'vie             '

代码:

;把每个单词的字母都变为大写字母
assume cs:code,ss:stack

data segment
			   ;0123456789ABCDEF
			db 'fil             '
			db 'edi             '
			db 'sea             '
			db 'vie             '
data ends

stack segment
			
stack ends
			

code segment

start:	
					mov ax,data
					mov ds,ax
					
					mov bx,0
					
					
					
					mov cx,4
					
upRow:				mov dx,cx ;临时把这个cx存起来,避免下面执行覆盖了
					
					mov cx,3;每个行执行三次
					mov si,0
					
upper:				mov al,ds:[bx+si]
					and al,11011111B
					mov ds:[bx+si],al
					inc si
					loop upper
					
					mov cx,dx ;再把最外层的循环次数拿出来
					add bx,16 ;每次要把bx的地址增加16,才能改变下一行的字符
					loop upRow

					mov ax,4C00H
					int 21H

code ends

end start

结果:
在这里插入图片描述
下面这种也是可以的,主要区别就是为了避免由于有过多层循环而导致寄存器不足,一般不推荐使用,自己分配内存太危险了。
mov ds:[40H],cx
mov cx,ds:[40H]

代码:

;把每个单词的字母都变为大写字母
assume cs:code,ss:stack

data segment
			   ;0123456789ABCDEF
			db 'fil             '
			db 'edi             '
			db 'sea             '
			db 'vie             '
			dw  99H  ;
data ends

stack segment
			
stack ends
			

code segment

start:	
					mov ax,data
					mov ds,ax
					
					mov bx,0
					
					
					
					mov cx,4
					
upRow:				;mov dx,cx 		;临时把这个cx存起来,避免下面执行覆盖了
									;这个放到dx里面。如果万一循环很多层的话,那寄存器就不够用了
					mov ds:[40H],cx 	;可用 mov ds:[40],cx 把cx存进去这个地址,到时再从对应地址取出来
					
					mov cx,3;每个行执行三次
					mov si,0
					
upper:				mov al,ds:[bx+si]
					and al,11011111B
					mov ds:[bx+si],al
					inc si
					loop upper
					
					mov cx,ds:[40H] ;再把最外层的循环次数拿出来
					add bx,16 ;每次要把bx的地址增加16,才能改变下一行的字符
					loop upRow
					
		
					mov ax,4C00H
					int 21H

code ends

end start

在这里插入图片描述
这个代码,与上面的区别不大。**最主要的一点就是通过使用内存地址来存放cx的值而不使用寄存器。**避免了在寄存器已经不够用的情况下不知如何求解。
这种情况也不是很好,最好的办法是通过栈来临时保存数据。

代码:

assume cs:code,ss:stack

data segment
			   ;0123456789ABCDEF
			db 'fil             '
			db 'edi             '
			db 'sea             '
			db 'vie             '
			dw 0
data ends



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




code segment

start:	
					mov ax,stack ;先把栈设置好
					mov ss,ax
					mov sp,32
					
					mov ax,data
					mov ds,ax
					
					mov bx,0
					
					
					
					mov cx,4
					
upRow:				
					push cx	 		;临时把这个cx存起来,避免下面执行覆盖了
									;这个放到dx里面。如果万一循环很多层的话,那寄存器就不够用了
									;可用 mov ds:[40],cx 把cx存进去这个地址,到时再从对应地址取出来
					
					mov cx,3;每个行执行三次
					mov si,0
					
upper:				mov al,ds:[bx+si]
					and al,11011111B
					mov ds:[bx+si],al
					inc si
					loop upper
					
					pop cx 		;再把最外层的循环次数拿出来
					add bx,16 	;每次要把bx的地址增加16,才能改变下一行的字符
					loop upRow
					
		
					mov ax,4C00H
					int 21H

code ends

end start

在这里插入图片描述
这种是在没有使用自己分配内存地址的,我们自己定义了一个栈,然后系统给我们分配了内存(这是我们向系统请求的,所以系统分配的会是安全的内存)


编程练习:将每个单词的前四个字母改为大写

代码:

;把每个单词的字母都变为大写字母
assume cs:code,ds:data,ss:stack

data segment
			   ;0123456789ABCDEF
			db '1. display      '
			db '2. brows        '
			db '3. replace      '
			db '4. modify       '
data ends



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




code segment

start:	
					mov ax,stack ;先把栈设置好
					mov ss,ax
					mov sp,32
					
					mov ax,data
					mov ds,ax
					
					mov bx,3;3的位置开始的
					
					
					
					mov cx,4    ;最外层有四个单词,需要执行四次
					
upRow:				
					push cx	 		;临时把这个cx存到栈中
									
					mov cx,4;每个单词前四个字母,需要执行四次
					mov si,0
					
upper:				mov al,ds:[bx+si]
					and al,11011111B
					mov ds:[bx+si],al
					inc si
					loop upper
					
					pop cx 		;再把最外层的循环次数拿出来
					add bx,16 	;每次要把bx的地址增加16,才能改变下一行的字符
					loop upRow
					
		
					mov ax,4C00H
					int 21H

code ends

end start

结果:没什么难度,跟上一题差不多。这里可以思考偏移地址除了使用bx寄存器之外,还可以怎么做。【还可以直接加3,就不用使用bx寄存器了】
在这里插入图片描述

以上这章很重要,都是关于数据在内存中如何存放和组织的,是整个汇编语言的核心部分也是基础部分来着的。


数据处理的两个问题:
1.数据从哪里来?
2.数据到哪里去?
如何判断数据的位数?
答案:利用寄存器来判断数据的位数

这里讲解如何不使用寄存器来确实数据的长度
在定义数据的前面加上
word ptr ;表示字型数据
byte ptr ;表示字节型数据
没什么新奇的地方,就是以后可以不用通过这个寄存器来判断位数了,可以通过代码来指明。
这个指定也适用于以前学过的指令.

	mov word ptr ds:[0],1 ;表示1将被翻译成字型数据
	mov byte ptr ds:[2],F ;表示1将被翻译成字节型数据
	inc word ptr ds:[0] ;这样的用法也是可以的

在这里插入图片描述
完整代码:

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

data segment
			   ;0123456789ABCDEF
			db 0FFH,0FFH,0FFH,0FFH
			db 0,0,0,0
			db 0,0,0,0
			db 0,0,0,0
data ends



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




code segment

start:	
					mov ax,stack ;先把栈设置好
					mov ss,ax
					mov sp,32
					
					mov ax,data
					mov ds,ax
					
					
					mov word ptr ds:[0],1 ;
					mov byte ptr ds:[2],0FH ;					

					mov ax,4C00H
					int 21H

code ends

end start

题目:

data segment
			db 'DEC'
			db 'Ken 01sen'
			dw 137		 ;排名改为38 ;12
			dw 40        ;增加70	 ;14
			db 'POP'     ;改为 'VAX' ;16
data ends

代码:

					mov ax,data
					mov ds,ax
					
					
					mov bx,0
					mov word ptr ds:[bx+12],38
					add word ptr ds:[bx+14],70
					mov byte ptr ds:[bx+16],'V'
					mov byte ptr ds:[bx+17],'A'
					mov byte ptr ds:[bx+18],'X'

除法指令:
有八位和十六位两种,在一个寄存器或者内存单元中。
被除数:默认放在 AX 中,或者 AX 和 DX 中
除数: 如果除数为 8 位,被除数则为16 位,默认放在AX中,如果除数为16 位,被除数则为 32 位,DX存放高 16 位, AX 存放低 16 位。
结果:
1.如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数
2.如果除数为16位,则AX存储除法的商,DX存储除法操作的余数。

mov ax,16 ;被除数
mov bl,3  ;除数
div bl   ;余数在ah,商在al中
;执行完可以发现 AX=0105010501是余数,05是商)

八位除法运算规则,被除数要为16位,除数为8位,除法运算后结果存在AX中,其中AH存放余数,AL存放商。

十六位除法运算规则,被除数要32位,被除数的三十二位高十六位存在DX中,低十六位存在AX中。除数要16位,除法运算后商存在AX中,余数存在DX中。

由此上面可得出一个规律,商总是存在存储低位被除数的寄存器,余数总是存在存储高位被除数的寄存器之中。

在这里插入图片描述
总结:其实也不难,具体采用16位除法还是8位除法,其实是由被除数和除数共同决定的,就是如果才去8位除法,被除数要能够在AX中存放的下,而除数也要能够在八位寄存器中放得下。

这个地方也有可能产生除法溢出,如果是1FFFFH/1=1FFFFH.但是这样,商是没有办法保存在AX之中的,这样就会产生一种叫除法溢出的情况。这里不必纠结,后面会讲到。


定义数据的几种形式:
1.db 定义字节型数据
2.dw 定义字型数据
3.dd 定义双字型数据【define dword,dw为double word的意思】

题目:把数据段的第一个数值除以第二个数值并把结果保存在第三个数值之中,
代码:

;用数据段中的第一个值除以第二个值,结果存放在第三个值中
assume cs:code,ds:data,ss:stack

data segment
				dd 100001
				dw 100
				dw 0
data ends  

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

code segment

start:	
					mov ax,stack ;先把栈设置好
					mov ss,ax
					mov sp,32
					
					mov ax,data
					mov ds,ax

					mov ax,ds:[0]		;设置被除数的低位
					mov dx,ds:[2]		;设置被除数的高位

					div word ptr ds:[4] ;除数,除以数据段的第二个数字
					
					mov ds:[6],ax      	;把商保存在数据段的第三个数字中

					mov ax,4C00H
					int 21H

code ends

end start

在这里插入图片描述


dup伪指令
平常我们定义100个字节型数据是怎么样子的呢

db 0,0.....

现在我们可以这样写:

db 100 dup(0)

写程序的一个标准模板:

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

data segment
			db 128 dup(0) ;定义128次 数值等于0 的字节型数据
data ends  



stack segment
			db 128 dup(0);定义128次 数值等于0 的字节型数据
stack ends




code segment

start:	
					mov ax,stack ;先把栈设置好
					mov ss,ax
					mov sp,128
					
					mov ax,data
					mov ds,ax
					





					
					mov ax,4C00H
					int 21H

code ends

end start

访问内存数据和存储数据的常用格式:

mov ax,ds:[0]
					mov ax,es:[0]
					mov ax,ss:[0]
					mov ax,cs:[0]
					
					mov ax,ds:[bx]
					mov ax,ds:[di]
					mov ax,ds:[dp]
					mov ax,ds:[bp]
					
					mov ax,ds:[bx+si]
					mov ax,ds:[bx+di]
					mov ax,ds:[bp+si]
					mov ax,ds:[bp+di]
					
					mov ax,ds:[bx+si+10+5]
					mov ax,ds:[bx+di+10]
					
					mov ax,ds:[bx+si+5]
					mov ax,ds:[bp+di+5]

					
					mov ax,1
					mov al,'a'
					mov al,0FFH
					and al,00001111B
					
					mov ax,bx
					mov dx,ax
					
					push bx ;只能操作字型数据
					pop dx	;只能操作字型数据
					
					push ds:[0]
					pop es:[0]
					

编程练习:
将 data 段中的数据按照如下格式写入到 table 中,并计算21年中的人均收入(取整)
代码:

;阅读性 初始化 数据从哪里来 数据放到哪里去
;cpu 将 cs:ip 所指向的信息当做指令
;cpu 将 ss:sp 所指向的地址当做栈顶
;cx寄存器,loop指令每执行一次,cx中的值会减1
;bx,一般用作偏移地址寄存器
;伪指令,告诉编译器,这里怎么翻译,那里怎么翻译
;start 伪指令,将程序的入口记录在exe文件的可描述信息中
;编译的规则,会影响内存的分配,会自动分配16的倍数个字节,1632,64……
;实际占用的字节为N,如果N%16==0,那么就占N个,如果N%16!=0,那么就是(N/16+1)*16
;建议 ds:[di]代表数据从哪里来, es:[di]代表数据到哪里去



;将 data 段中的数据按照如下格式写入到 table 中,并计算21年中的人均收入(取整) 
;结果也按照下面的格式保存在table中
assume cs:code,ds:data,ss:stack

data segment
			
			db '1975','1976','1977','1978','1979','1980','1981'
			db '1982','1983','1984','1985','1986','1987','1988'
			db '1989','1990','1991','1992','1993','1994','1995'
			;年份
			
			dd 16,22,382,1356,2398,8000,16000,24486,50065,97479,140417,197514
			dd 345988,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
			;收入
			
			dw 3,7,9,13,28,38,130,228,476,778,1101,1442,2258,2793,4037,5635,8226
			dw 11542,14438,15257,17800
			;员工人数
data ends  


table segment		       ;0123456789ABCDEF
				db  21 dup('year summ ne ?? ')
table ends


stack segment
			db 128 dup(0);定义128次 数值等于0 的字节型数据
stack ends




code segment

start:	
					mov ax,stack ;先把栈设置好
					mov ss,ax
					mov sp,128
					
					mov ax,data  ;设置数据从哪里来 
					mov ds,ax
					
					mov ax,table ;结构化数据的位置
					mov es,ax

					mov si,0	;年份的偏移地址  			ds:[si]
					mov di,84   ;公司收入的偏移地址			ds:[di]
					mov bx,168  ;公司员工人数的偏移地址		ds:[bx]
					mov bp,0	;数据放到哪里去				es:[bp]
					
					
					mov cx,21
					
inputTable:			push ds:[si] ;改变一个年份
					pop es:[bp]
					push ds:[si+2]
					pop es:[bp+2]
					
					; 0123456789ABCDEF
					;'year summ ne ?? '
					
					mov ax,ds:[di]  ;进行16位除法的安排
					mov dx,ds:[di+2]
					mov es:[bp+5],ax ;
					mov es:[bp+7],dx ;
					
					push ds:[bx]
					pop es:[bp+0AH]
					div word ptr ds:[bx]
					mov es:[bp+0DH],ax ;放置商
					
					add si,4
					add di,4
					add bx,2
					add bp,16
					loop inputTable
					
					
									;转移一个年份需要四次 mov cx,4(字节) 或 2次(字型)
									;这里可以设置cx为2,然后下面利用ax;来传值
									;或者改为,这种太麻烦,且需要寄存器太多
									;                   push cx
									;                   push si
									;                   push bp
									;SetYear:			mov al,ds:[si] ;数据从哪里来
									;					mov es:[bp],al ;数据放到哪里去
									;					inc si
									;					inc bp
									;					loop SetYear
									;                   pop bp
									;                   pop si
									;                   pop cx
									;建议使用这个方法
							

					mov ax,4C00H
					int 21H

code ends

end start

经过结构化的数据,编写代码去访问他们的时候,特别方便。我们可以将这些零散的数据结构化
偏移的思想,数据结构很重要。不要去被动的接受数据,将这些数据结构化,为了我们以后编写代码提供了方便。
数据从哪里来
数据到哪里去
程序、代码的好坏与数据组织的合不合理有很大关系,结构好不好很关键。
数据结构很重要,上面这道题对于数据的组织还算是比较合理的,所以,数据结构非常非常重要


转移指令原理:
可以修改IP 或者 同时修改 CS 和 IP
【jmp:无条件转移指令】
【loop:条件转移指令,cx】
理解cpu执行转移指令的原理。

OFSET 操作符:取出后面加的 伪指令 的地址。
指令执行过程:
1.cpu从 cs 和 ip 所组合出来的地址 读取指令,将指令放到指令缓存器中
2.ip=ip+所读指令的字节数
3.执行指令缓存器中的内容,跳转到第一步

JMP 跳转指令编译后的 机器码 和指令的长度有关。
通过这么一种方式,CPU在执行JMP指令的时候,并不需要 跳转的 目的地,就可以实现对 IP 寄存器的修改。
实验:
在这里插入图片描述
此时,JMP 指令偏移地址后面第三部分是 EB04(JMP指令与它要跳转的指令中间夹着4个字节)
在这里插入图片描述
此时,JMP指令偏移地址后面第三部分是 EB07(JMP指令与它要跳转的指令中间夹着7个字节)
在这里插入图片描述
此时,JMP指令偏移地址后面第三部分是EB09(JMP指令与它要跳转的指令中间夹着9个字节)

结论:标号地址 - jmp指令后第一个字节的地址
JMP指令跳转范围
根据上面的实验,我们得知每次只有一个字节来存储跳转指令中间的字节数【-128-127】,8位位移,但是万一我们要跳转的指令中间很多怎么办呢?那么我们就需要使用16位位移。
往上跳是负数,往下跳是正数。

jmp s     		;使用八位位移
jmp near ptr s	;使用十六位位移

实验:分析一个奇怪的程序【非常重要,到目前,你有没有理解这个程序,就看你能不能看懂这个程序,能不能分析它的运行过程】
代码:

assume cs:code


code segment

		mov ax,4C00H
		int 21H
start:  
		mov ax,8
s:		nop  		;cpu遇到nop指令,什么都不做,占用1个字节
		nop
		
		mov di,OFFSET s
		mov si,OFFSET s2
		mov ax,cs:[di]
		mov cs:[di],ax
		
s0:		jmp short s

s1:		mov ax,0
		int 21H
		mov ax,0
		
s2: 	jmp short s1  ;短转移指令 -128-127 EBXX
		nop
	
code ends

end start

loop条件转移指令:当cx寄存器中的值不等于0时进行条件,且cx-=1;
jcxz 条件转移指令:当cx寄存器中的值等于0时,进行jmp。
j =jmp
cx=cx寄存器
z= zero =0

所有的条件转移指令 都是 短转移 位移范围 -128-127
经过编译后 机器码中 包含了位移的范围
EBXX
编译器在编译的时候计算的
标号处的偏移地址 - jmp指令后第一个字节的地址

当 cx=0的时候进行jmp
位移范围 -128-127
编译器编译的时候计算

题目:
补全程序,利用jcxz指令,实现 在内存 2000H 段中查找第一个值为0的字节,然后,将它的偏移地址 存储在dx中
代码:

;补全程序,利用jcxz指令,实现 在内存 2000H 段中查找第一个值为0的字节,然后,将它的偏移地址 存储在dx中
assume cs:code

data segment
			
data ends  




stack segment
			db 128 dup(0);定义128次 数值等于0 的字节型数据
stack ends


;字节型数据, cx= ch + cl ,cx=0,隐藏 cl=0 and ch =0
;保持一个八位寄存器为0,然后,只要剩下的读到 0 了,那么问题就解决了

code segment

start:	
			mov ax,2000H
			mov ds,ax
			mov bx,0

s:			mov ch,0   ;保持ch为0
			mov cl,ds:[bx] ;把地址的数值移动到cl里面
			jcxz ok ;如果发生跳转,证明 cl=0,也就是cx也等于0 ,发生跳转
			inc bx
			jmp short s


ok:			mov dx,bx ;跳转过来,记录下bx即偏移地址即可解题
			

			mov ax,4C00H
			int 21H

code ends

end start

loop 指令:条件转移指令,所有条件转移指令都是短转移,对应的机器码包含了 -128 - 127,编译器编译的时候计算出来的
cx=cx-1
判断cx是否等于0
不等于 0 则进行跳转;
等于 0 则继续执行下面的指令。

代码:

;补全程序,利用jmp指令,实现 在内存 2000H 段中查找第一个值为0的字节,然后,将它的偏移地址 存储在dx中
assume cs:code

data segment
			
data ends  




stack segment
			db 128 dup(0);定义128次 数值等于0 的字节型数据
stack ends


;字节型数据, cx= ch + cl ,cx=0,隐藏 cl=0 and ch =0
;保持一个八位寄存器为0,然后,只要剩下的读到 0 了,那么问题就解决了

code segment

start:	
			mov ax,2000H
			mov ds,ax
			mov bx,0

s:			mov ch,0   ;保持ch为0
			mov cl,ds:[bx] ;把地址的数值移动到cl里面
			inc cx     ;因为loop指令是要先将cx中的值减去1的,这样就可以抵消掉执行过程中对原来cx寄存器中数值的影响
			inc bx
			jmp short s ;jmp指令,一旦cx等于0,就继续执行下面的指令,这个时候
						;要消除bx的影响

ok:			
			dec bx    ;自减1
			mov dx,bx ;跳转过来,记录下bx即偏移地址即可解题
			
			mov ax,4C00H
			int 21H

code ends

end start

这个题目,难度不是很大,最主要的是要要清楚 loop 指令 和 jcxz 指令的执行原理和他们之间的区别。


题目:内存地址空间中,B800H - BFFFH 共 32kb的空间,为 80 * 25彩色字符模式的显示缓冲区。向这个地址写入数据,写入的内容将立即显示在显示器上。

偶数地址存放 字符的 ASCII码
寄书地址存放 字符的颜色,也就是字符的属性。
一行能够存放80个字符(160个字节),总共能存放25行。
在这里插入图片描述
代码:


;编程,在屏幕中间分别显示 绿色、绿底红色、白底蓝色 的字符串 'welcome to masm!'
;数据的组织
;数据的处理   一个字符在屏幕上占用2个字节 低地址存放字符的 ASCII码 高地址存放字符的属性

assume cs:code

data segment
			   ;0123456789ABCDEF            ;7654 3210
			db 'welcome to masm!'          	;0000 0000
			db 00000010B ;绿色             ; rgb  rgb
			db 00100100B ;绿底红色         ; 背景 前景
			db 01110001B ;白底蓝色         ;7 闪烁
data ends                                   ;3 高亮


stack segment
			db 128 dup(0);定义128次 数值等于0 的字节型数据
stack ends

code segment

start:

			mov ax,stack
			mov ss,ax
			mov sp,128
			
			mov bx,data
			mov ds,bx
			
			mov bx,0B800h   
			mov es,bx         


			mov si,0   
			mov di,160*10+20*2 ;设置屏幕的位置
			
			mov bx,16
			mov dx,0
			
			
			mov cx,3
			
showMasm:	push bx
			push cx
			push si
			push di
			
			
			
			mov cx,16  ;一行16个字节
			mov dh,ds:[bx] ;存放字符的颜色
			
showRow:	mov dl,ds:[si]
			mov es:[di],dx
			add di,2
			inc si
			loop showRow
			
			pop di
			pop si
			pop cx
			pop bx
			add di,160
			inc bx
			loop showMasm

			mov ax,4C00H
			int 21H

code ends

end start

计算指令的长度可以利用两个ip地址之间的差值。


CALL指令 和 RET 指令
位移的方式:将转移的目的地址存放在内存中。
当执行 RET 指令时,相当于 执行 pop ip
当执行 RETF 指令时,相当于执行 pop is pop cs
栈也是一段内存,pop指令会修改栈顶指令,ret retf 指令也会修改栈顶指令。

;补全程序,实现从内存 1000H:0000 处开始执行指令

assume cs:code

data segment

data ends      


stack segment
			db 16 dup(0)
stack ends

code segment

start:

			mov ax,stack
			mov ss,ax
			mov sp,128
			
			mov ax,1000H
			push ax
			mov ax,000
			push ax
			
			retf

			mov ax,4C00H
			int 21H

code ends

end start

指令执行的过程:
1.cpu从 cs :ip 所组合出来的地址 读取指令,读到 指令缓存器中
2.ip=ip + 所读指令的字节数 ip指向了下一条指令
3.执行 指令缓存器中的内容,回到第一步

call指令 :
1.push ip ;此时push的ip是call指令的下一条指令的 IP
2.jmp near ptr 标号

call指令位移的转移方式 位移的方式
16位位移
位移= 标号处的偏移地址 - call 指令后 第一个字节的地址
-32768 - 32767

转移的目的地址在指令(机器码)中的call 指令
jmp far ptr 标号
call s 机器码中包含了位移
转移后的第一个字节的地址 + 位移

call far ptr 标号 相当于执行下面三条指令
push cs ;call指令的下一条指令的段地址就被放在栈中
push ip ;call指令的下一条指令的偏移地址就被放在栈中
jmp far ptr 标号【标号所在的段地址和标号所在的偏移地址】cs=标号所在段地址 ip = 标号所在的偏移地址


call 16位寄存器
相当于:
push ip
jmp 16位寄存器 ip= 16位寄存器中的字型数据
把call指令的下一条指令的偏移地址就是 当前ip 的值。

在这里插入图片描述

在这里插入图片描述
查看栈,里面的数据是 05,也就是call指令下一个偏移地址的 ip
在这里插入图片描述
转移地址在内存中的call指令
指令格式:
call word ptr 内存单元地址
相当于执行了
push ip
jmp word ptr 内存单元地址
**
**
指令格式:
call dword ptr 内存单元地址
相当于执行了
push cs
push ip
jmp dword ptr 内存单元地址

call指令的使用:与 ret retf 配合起来使用。

assume cs:code

data segment

data ends      


stack segment
			db 16 dup(0)
stack ends

code segment

start:

			mov ax,stack
			mov ss,ax
			mov sp,128
			
			call s          ;push ip  代码的组织,通过call指令和ret 指令 进行逻辑的切割
			mov ax,1000H
			push ax
			mov ax,000
			push ax
			
s: 			mov ax,1000H
			ret             ;pop ip 从上面跳转到下面来

			mov ax,4C00H
			int 21H

code ends

end start


;另外的方式
把这些 s s1 s2等的这些标号都放在内存地址中
    mov ax,OFFSET s
    mov ds:[0],ax
    mov ax,OFFSET s1
    mov ds:[1],ax
 等等,诸如此类的做法

call 指令的原理
将位移保存在机器码中
将转移的目的地址存放在内存中
将转移的目的地址存放在机器码中 call far ptr s
将转移的目的地址存放在寄存器中 call ax call bx

call指令 组织代码 对逻辑进行切割
call 程序 只要根据提供的参数 进行处理

mov si,0
mov di,16010+202

call show_string

mov si,5
mov di,16030+202

call show_string
组织数据的目的 是为了我们能够更好的去组织代码

push pop
防止和外面的寄存器产生冲突

mul 乘法指令
call get_cube
希望call程序处理数据完毕后,产生的数据在call 程序外面还能够使用

返回值 通过寄存器 去传递这个返回值。


;乘法指令
;两个相乘的数字 要么都是8位 要么都是168位乘法 16位乘法

;如果是八位乘法,一个默认在 al 中,一个在其他八位寄存器中,结果在ax中
;如果是十六位乘法,一个默认在 ax 中,一个在其他的16位寄存器中,结果 低十六位在 ax 中,高 十六位 在 dx中
assume cs:code,ds:data,ss:stack

data segment
			db 128 dup(0)
data ends

stack segment stack
			db 128 dup(0)
stack ends

code segment

start:
		mov ax,stack
		mov ss,ax
		mov sp,128
		
		mov ax,data
		mov ds,ax
		
		mov ax,0
		
		mov al,100
		mov bl,10
		mul bl 
		
		
		mov ax,4C00H
		int 21H

code ends
end start

也可以试一下十六位乘法,注意观察AX和DX的值

		mov ax,100
		mov bx,10
		mul bx

int(38/10) =3 ;int() 可以看做是一个描述性运算符,相当于取整
rem(38/10)=8 ; rem() 可以看做是一个描述性运算符,相当于取余

如何解决除法溢出?
提示公式: DX AX
X:被除数,范围 [0,FFFF FFFFH]
N:除数 ,范围 [0,FFFFH]
N:X的高16位
L:X的低16位
公式: X/N =int(H/N)*65536+[rem(H/N)*65536+L]/N
公式:X/H = int(H/N)*10+[rem(H/N)*10+L]/N
在这里插入图片描述


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值