8086寻址方式:段地址:偏移地址 内存地址=段地址*16(或左移动1位,末尾补0)+偏移地址=实际地址
如:2000:4700=>20000+4700=24700h这个地址,原因为:cpu总线为20,内存地址为32位,所以需要使用此寻址方案
使用DOSbox的方法:
1.打开dosBOX软件
2.会出现一个z盘符,我们需要把虚拟的盘挂载到一个指定的文件夹
挂载方法:mount c c:\masm 把工作目录挂载到已经存在的masm文件夹,这样在本地都可以对文件进行管理了
3.debug.exe 的使用:不区分大小写
-q 退出
-r 查看,修改CPU寄存器的内容
-r ax 查看Ax寄存器的值,输入值就为更改(默认为以16进制输入和显示)
-d 查看内存中的内容
可以看到预设内存中的128个字节的内容,-d 可以看到后边128个字节
-d 段地址:偏移地址 可以查询这个地址后面的128个字节内容
-e 改变内存中的内容
-e 开始地址(段地址:偏移地址) data1 data2 …(最大数为FF)
-e 开始地址(段地址:偏移地址)
这样可以一个字节一个字节的进行替换修改,按空格修改下一个字节,回车结束
-u 将内存中的机器指令翻译成汇编指令
-u 开始地址(段地址:偏移地址)可以把这片地址看着是汇编指令的机器码,通过-u能解析为汇编指令
cs寄存器是用来存放代码的,ip寄存器为偏移指针
-a 以汇编指令的格式在内存中写入机器指令
-a 073f:0100(cs:ip默认的起始地址)mov ax,0123
mov ax,bx
add ax,bx
-t 执行机器指令
-t 自动执行CS:1P位置的指令。
修改CS的值: -r cs
修改ip的值:-r ip
8086CPU,是16位,16位作为一个字,把数据放寄存器时,一个字节为8位,高8位放高字节,低8位放低字节,当指针指向一个内存地址时,指向的是低位
当说:0地址单元存放的字节类型数据是对应0地址的8位数据
当说,0地址字节存放的字型数据是对用地址0,1地址中的数据,0地址存放的是低位,1地址存放的是高位
注意:字节型数据是8位,字型数据是一个字,为16位
mov ax,4e20=====>aL=4e,ah=20
jmp 转移指令:可同时修改CS:ip
jum 段地址:偏移地址 这样下一条指令就会转移到此地址进行执行
或者单独修改IP的方法:
jmp ax(某一合法寄存器)
DS段寄存器
通过汇编指令读取内存单元数据
解决方案:用DS寄存器存放要访问的段地址,有[]给出偏移量(电路这样设计的)
例:注意只能通过间接赋值的方式,而不能使用mov ds,1000H
mov bx,1000H //把段地址先存放到通用寄存器BX中
mov ds,bx //把1000H段地址间接赋值给DS
mov al,[0] //将1000:0 数据存到al中
mov [0],al //将al中的数据写到1000:0中
在内存中读取两个两个字节(1个字)的数据时要先读高位,再读低位(显示时是低位在前,高位在后)
关于段地址和偏移量,是又程序员自己定义的,可以多样性
如:123B0H,起始偏移地址:123BH,起始偏移量0000H
123B0H,起始偏移地址:1230H,起始偏移量0080H
案例:
1.累加数据段中的前三个单元中的数据:段地址123BH(单元指的是8位的数据)
mov ax,123BH //段地址放入通用寄存器
mov ds,ax //把地址赋值给段地址寄存器
mov al,0 //初始化ax低位寄存器
add al,[0] //把123B0h的数据与ax寄存器的低位中的数相加
add al,[1] //把123B1h的数据与ax寄存器的低位中的数相加
add al,[2] //把123B2h的数据与ax寄存器的低位中的数相加
2.累加数据段中的前三个字型数据:段地址123BH(字型数据指的是16位的数据)
mov ax,123BH //段地址放入通用寄存器
mov ds,ax //把地址赋值给段地址寄存器
mov al,0 //初始化ax寄存器
add aX,[0] //把123B0h、123B1h的数据与ax寄存器中的数相加
add aX,[2] //把123B2h、123B3h的数据与ax寄存器中的数相加
add aX,[4] //把123B4h、123B5h的数据与ax寄存器中的数相加
mov指令:
寄存器赋值:
mov ax,8h
mov ax,bx
mov ax,[0]
mov al,[0] //一个字节8位赋值
mov ah,[0] //一个字节8位赋值
内存赋值
mov [0],ax
mov [0],ds
段寄存器赋值
mov ds,ax
add指令:
结果放寄存器:
add ax,8
add ax,bx
add ax,[0]
结果放内存单元:
add [0],ax
内存不能直接相加,必须使用寄存器
add [1][2] //语法错误
通用寄存器不能与DS段寄存器直接相加
add ds,ax //语法错误
sub减法运算,规律与add用法相同
sub 被减数 减数 //sbu ax,bx => ax=ax-bx
对于通用寄存器,直接使用就是16位,如ax
分开使用就是 8位,如al
栈的操作:
栈是先进后出,栈指针默认指向栈顶的下一个地址
入栈指令:push
出站指令:push
知识点:
1.cpu如何知道一段内存被当做栈使用?
8086CPU中,有两相关栈的寄存器:
栈段寄存器:SS
栈指针偏移寄存器:SP
栈操作:栈操作都是16位的数据,占两个单元内存空间
设置1000h:10h位栈区域(不包括10h)
mov ax,1000h
mov ss,ax //赋值栈段寄存器
mov sp,10h //赋值栈偏移的大小
栈内存空间为1000:0h—1000:fh 共计16个字节空间(内存单元空间)
2.执行push和pop的时候,如何知道哪个单元是栈顶单元
SS:SP无时无刻都指向栈顶的下一个地址
超界问题:
8086CPU计算机本身是不能保证对栈的操作不会越界.只知道栈顶的位置,不知道程序安排的栈空间有多大。
越界带来的后果:
入栈的数据太多导致超界,导致其他空间内容被改写
出栈时栈空了仍然继续出栈导致超界
杜绝:
在编程时,要合理安排栈空间大小
8086CPU提供的栈操作机制:
1.在ss,sp中存放栈顶的段地址和偏移地址,入栈和出栈指令根据SS:SP指示的地址,按照栈的方式访问内存单元。
2.push指令的执行步骤:
1.SP=SP-2
2.向SS:SP指向的字单元中送入数据
3.POP指令执行的步骤:
1.从SS:SP指向的字单元中读取数据
2.SP=sp-2
栈操作案例:
mov ax,1000h
mov ss,ax
mov sp,0010H
mov ax,001AH
mov bx, 001BH
push ax //Ax的数据入栈
push bx //BX的数据入栈
pop ax //栈顶的数据出栈,数据放入AX,同时栈指针往上移动2个内存单元
pop bx //栈指针指向的栈顶出栈,数据放入BX中
结果:ax,bx的值通过先入后出的方式重新对ax,bx赋值,同时也在没有中间变量的情况下,做了数据的交换
栈的总结:
push\pop实质是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中
给出的,而是由SS:sp指出的
执行push和pop指令时,sp中的内容会自动改变
段的总结:
基础:
物理地址=段地址*16+偏移量地址
做法:
编程时,可以根据需要将一组内存单元定义为一个段
可以将起始地址为16的倍数,长度为N(N<=64k)的一组地址连续的内存单元,定义为一个段
将一段内存定义为一个段,用一个段的地址指示段,用偏移地址访问段内的单元—在程序中可以完全由程序员安排
数据段:DS定义段 [X]定义需要偏移的内存单元的数据
代码段:CS中定义代码段地址,IP定义偏移地址,代码将从CS:IP开始执行
栈段:SS定义栈段,SP定义偏移地址,SS:SP指向栈顶==》栈空间的后一个单元
数据段和栈段可以共用内存空间:
mov bx,1000h
mov ds,bx
mov ss, bx
mov sp,20H
汇编语言程序格式
assume cs:abc //代码段需要与代码寄存器CS关联,要从cs寄存器开始
abc segment //代码的开始 代码段名为abc
mov ax,2
add ax,ax
mov ax,4c00h //代码返回值,固定格式
int 21h //调用中断,固定格式
abc ends //代码段的结束
end //程序的结束
汇编程序的编译:后边加";"后,不进行提示,直接执行到结束。
1.使用编辑器,编辑代码,文件名为.asm
2.使用masm.exe 编译aa.asm,会生成aa.obj
3.使用link.exe 链接aa.obj,生成可执行文件aa.exe
debug检查程序执行结果:
debug aa.exe
cx寄存器为程序占用的空间大小寄存器
-g(Go):在debug下快速的执行,直到遇到断点或者程序结束,然后就可以查看寄存器和内存单元,查看结果。
-g 程序段地址:偏移地址 程序会在执行到此地址,一般选择为程序输入完成后的下一行地址
汇编语法规定内存单元使用[X]
MOV AX,[0] //段地址在ds中,偏移地址在0这个偏移量地址中存放的数据,操作数是ax为字
mov al, [bx] //段地址在ds中,偏移量在bx寄存器地址中存放的数据
约定1:
内存单元或寄存器中的内容,在使用书面表达的时候使用()
ax中的内容为0010H描述为:(ax)=0010H
2000:1000处的内容为0010H 描述为:(21000H)=0010H
MOV AX,[2] 描述为:(AX)=((Ds)*16+2)
mov [2],ax 描述为:((ds)*16+2)=(ax)
add ax,2 描述为:(ax)=(ax)+2
add ax,bx 描述为:(ax)=(ax)+(bx)
push ax 描述为:(sp)=(sp)-2 ((ss)*16+(sp))=(ax)
pop ax 描述为 (ax)=((ss)*16+(sp) (sp)=(sp)+2
约定2:idata表示常量
书写格式为:mov ax,[idata],实际格式如:mov ax,[0]; mov ax [1];…
mov bx ,idata 实际格式如:mov bx,1;mov bx,2;需要输入实际的数据
非法指令:mov ds,idata 格式如:mov ds,1,mov ds 2;
CX寄存器:用于存储LOOP值,当遇到loop后,先检查cx的值是否为0,若为0就向下执行,否则执行跳转到loop标号处执行。
inc ax 为自身ax累加1;
注意:[0]取值的问题
mov al,[0]
MOV BL, [1] 改写:mov bl,ds:[0]
编译器进过编译后,会变成如下代码:
mov al,0
MOV BL, 1
结果变成了直接赋值,导致程序错误。
解决办法:
mov ax,2000h
mov ds,ax //给数据段赋值
mov bx,0
mov al ds:[bx]//段地址:偏移量 -->这样就不会出现编译错误的情况
—[bx]为bx寄存器所指向的地址中的值
当使用mov ax,[常量]的时候,需要改成 mov ds:[1]
而使用mov ax, [寄存器]的时候,则能正确编译。
案例:编程计算以下8个数据的和,结果放在ax寄存器中
0123H 0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H
assume cs:code
code segment
dw 0123H ,0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H//将数据放入代码段
abc: mov bx ,0 //数据的偏移量(对于dw列表,dw属于数据存放的区域)
mov ax,0 //初始化结果保存的寄存器
mov cx,8 //loop循环的次数寄存器
s:add ax,cs:[bx] //获取到数据区的元素,段地址:偏移量
add bx,2 //因为0123H这些数据都是16位的,所以要偏移2,才能指向下一个元素
loop s
mov ax,4c00h
int 21h
code ends
end abc //abc是告诉编译器程序存储的空间开始的位置,进行一个标号。
//这样在程序执行的时候,就错开了存放数据的区域
案例:利用栈,将数据逆序存放。
0123H ,0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H
思路:
1.程序运行时,定义的数据存放在cs:0-cs:f单元中,共8个字节单元
2.依次将这8个数据入栈,然后依次出栈到这8个字单元中,从而实现数据的逆序存放
3.需要定义空数据,如下的dw 0,0,0,0,0,0,0,0
assume cs:codesg
codesg segment
dw 0123H ,0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
abc: mov ax,cs
mov ss,ax
mov sp,30h
mov bx,0
mov cx,8
s:push cs:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0:pop cs:[bx]
add bx,2
loop s0
codesg ends
end abc
将数据、代码、栈放入不同的段
assume cs:code,ds:data,ss:stack
data segment
dw 0123H ,0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H
deat ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
code segment
#初始化各段寄存器
start: mov ax,stack
mov ss,ax
mov sp,20h
mov ax,data
mov ds,ax
#入栈
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
code ends
end start
在正常程序中:
数据存放的地址为DS+100H,100h为程序数据的前缀,如al
DS=075A0H,数据存放的地址段为:075A0H+100=076A0 内存单元开始地址为076A:00
例子:
assume cs:code,ds:data
data segment
db ‘unix’
db ‘fork’
data ends
code segment
start: mov al,‘a’
mov bl,‘b’
mov ax,4c00h
int 21h
code ends
end start
在执行完成debug时,查看‘unix’'fork’的地址:
DS=075A0h
数据不是从075A0:0 开始
而是从DS+100H=075A0H+100h(程序前缀地址)=076A:0
在实际开发中,常数:
11就为十进制11
11h为十六进制的数据
大小写转换规律:大写+20h=小写 小写-20H=大写
b 62h 0110 0010
B 42h 0100 0010 //区别:在第六位不一样
当大写转小写时,只需要与(and) 1101 1111就可以进行转换
0110 0010 b
0010 0000 AND
0110 0010 B
0100 0010 B
1101 1111 OR
0100 0010 b
and,or的用法,每一位进行计算
and a,b ===>a=a and b
or a,b ===>a=a or b
代码实现:大小写转换
assume cs:codesg,ds:datasg
datasg segment
db ‘Basic’ //字符串表示法
db ‘INdifseDEeD’
datasg ends
codesg segment
start:
mov ax,datasg //程序执行后,系统分配的地址单元DS寄存器的值
mov ds,ax
#第一串:小写转大写
mov bx,0 //基础地址,从0开始
mov cx,5 //有5个字符
s:mov al,[bx] //取出B
and al,11011111b
mov [bx],al //结果又保存在[bx]中
inc bx
loop s
#第二串:大写转小写
mov bx,5 //第二个字符串在第一个的基础上偏移5个单元
mov cx,11 //第二串有11个字符
s0:mov al,[bx]
or al,0010 0000b
mov [bx],al
inc bx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
[bx+idata]的含义:
表示一个内存单元,偏移地址为(bx)+idata
mov ax,[bx+200]的含义:
1.将一个内存单元的内容送入ax
2.这个内存单元的长度为2字节
3.内存单元的段地址在ds中,偏移地址为200h,即为:ds:200+(bx)这个内存中的值
4数学描述为:(ax)=((ds)*16+200+(bx))
其他写法也可以为:
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
[BX+idata]操作内存的案例:
大小写转换优化:当两个字符串长度相同时的优化,优化原因,当长度相同时,有很多相同的代码
assume cs:codesg,ds:datasg
datasg segment
db ‘BasIc’
db ‘mAdMs’
datasg ends
codesg segment
start:
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
#以下为代码优化
s:mov al,[bx] //取第一串第一个字符
and al,1101 1111b
mov [bx],al //and后结果放回bx地址的内存中
mov al,[5+bx] //取第二串第一个字符
or al,0010 0000b
mov [5+bx],al //or 后结果放回第二个字符串中的第一个字符的内存中
inc bx
loop s
codesg ends
end start
SI,DI寄存器,不能拆分成两个8位:主要用于内容的copy
SI寄存器:源变址寄存器source index
DI寄存器:目标变址寄存器 destination index
案例:用寄存器SI和DI实现将字符串‘welcome to masm!'复制到它后边的数据区域
分析:
源数据的起始地址为datasg:0
目标数据的起始地址为datasg:16
用ds:si指向要复制的原始字符串
用ds:di指向目标空间
然后用循环来完成复制
assume cs:codesg,ds:datasg
datasg segment
db ‘welcome to masm!’
db ‘…’
datasg ends
codesg segment
start:
mov ax,datasg
mov ds,ax //数据段地址
mov si,0 //偏移量为0,指向源字符串第一个地址
mov di,16 //偏移量为16,指向目标地址的第一个地址
mov cx,8 //循环次数计数器
s:mov ax,[si]//把源的第一个字符给通用寄存器
mov [di],ax //通用寄存器把第一个字符放入目标第一个地址里边
add si,2 //因为SI和DI都是16位的地址,copy的时候也是copy两个字节,
add di,2 //而一个字符为一个字节,所有这里要+2,16个字符只需要copy8次
loop s
mov ax,4c00h
int 21h
codesg ends
end start
[BX+SI]和[BX+DI]方式指定地址
[BX+SI]表示内存单元:偏移地址为(bx)+(SI),即bx中的数值加上SI中的数值
mov ax,[bx+si]的含义:
1.将一个内存单元的内容送入ax
2.这个内存单元的长度为2个字节(字单元),存放1个字
3.偏移地址为:bx中的数值加上SI中的数值
4.段地址在ds中,偏移地址在0这个偏移量地址中存放的数据
数学化描述为:
(ax)=((ds)*16+(bx)+(si))
其他写法为:
mov ax,[bx][si]
[bx+si+idata]和[BX+DI+idata]方式指定地址,三个地址中的数据叠加后的偏移量,段地址在DS中
[BX+SI+idata]表示内存单元
偏移地址为(bx)+(si)+idata,即:bx中的数值+SI中的数值+idata
mov ax,[bx+si+idata]的含义:
1.将内存单元的内容送入ax
2.这个内存单元的长度为2字节,存放1个字
3.偏移地址为bx中的数值+si中的数值+idata,段地址在ds中,偏移地址在0这个偏移量地址中存放的数据
数学表达式为:
(ax)=((ds)*16+(bx)+(si)+idata)
其他写法:
mov ax,[bx+200+si]
mov ax,[200+bx+si]
mov ax,200[bx][si]
mov ax,[bx].200[si]
mov ax,[bx][si].200
mov ax,[bx][si]==>idata=0
灵活运用不同的寻址方法,案例:
编程将datasg段中的每个单词的头一个字母改为大写字母
assume cs:codesg,ds:datasg
datasg segment
db ‘1.file…’ //都为16个字符
db ‘2.edit…’
db ‘3.send。。。。。。。。…’
db ‘4.view…’
db ‘5.option…’
db ‘6.help…’
datasg ends
codesg segment
start:
mov ax,datasg //把随机的DS段地址赋值给ax
mov ds,ax //ds将指向系统指定的段地址
mov bx,0 //第一行数据第一个单元
mov cx,6 //需要循环6次
s:mov a,[bx+3]//第一行的首地址+3指向文件头第一个单词首字符
and al,1101 1111b //转大写运算
mov [bx+3],al //转换完成,放回原位
add bx,16 //bx+16将转换到下一个字符串中的首地址
loop s
codesg ends
end start
案例:灵活运用不同的寻址方式
编程将datasg段中的每个单词改为大写字母
assume cs:codesg,ds:datasg
datasg segment
db 'ibm '//16个字符
db 'dec '//可以看出4行3列的二维数组:4*3,
db 'dos '//这样需要两层循环可以解决
db 'vax '//先循环改变第一串每个字母为大写,然后
//将地址+16转到下一行,然后再调用第一次的循环
datasg ends
codesg segment
start:
mov ax,datasg
mov ds,ax //存入段地址
mov bx,0 //偏移量初始化为0
mov cx,4 //循环计数
s0:mov si,0 //将源寄存器初始化为0
mov cx,3 //---->这个cx只有一个,第一串循环完了,就为0,
//当第二个循环来了,会继续减,判断当然不为0,这样程序乱了-----》需要解决
s:mov al,[bx+si] //第一次偏移量为[0+0]指向首行第一个字符
and al,1101 1111b//运算转换为大写
mov [bx+si],al //将转换后存入[0+0]首行第一个字符
inc si //源寄存器+1
loop s //循环执行第一行改为大写
add bx,16 //第一行循环完成,
loop s0
二重循环解决一个cx的问题:
assume cs:codesg,ds:datasg
datasg segment
db 'ibm ’
db 'dec ’
db 'dos ’
db 'vax ’
datasg segment
start:
mov ax,datasg
mov ds,ax
mov bx,0 //初始化偏移量
mov cx,4 //循环计数
s0:mov dx,cx //外层循环次数存入dx通用寄存器,值为4,每次到这来dx会随着CX-1
此行可以改为 #mov ds:[40H],cx//解决寄存器稀缺问题
mov si,0 //初始化源寄存器
mov cx,3 //内存循环赋值,循环3次
s1:mov al,[bx+si] //取[0+0]偏移处的地址
and al,1101 1111b//运算转换为大写
mov [bx+si],al //运算转换为大写存入
inc si //偏移+1指向第一行ds:01处的单元
loop s1 //循环完成第一行的大写转换
add bx,16 //第一行完成,转换到第二行的偏移量为16
mov cx,dx //dx存放的是内循环没有执行时候的值,即内循环的次数,赋值给循环计数器
此行可以改为#mov cx,ds:[40H] //解决寄存器稀缺问题:
loop s0
codesg ends
end start
在开发中,寄存器是非常稀缺的资源,所以需要将寄存器存储改为内存空间存储
也可以使用栈保存数据:解决寄存器稀缺问题
assume cs:codesg,ds:datasg
datasg segment
db 'ibm ’
db 'dec ’
db 'dos ’
db 'vax ’
datasg segment
stacksg sement
dw 0,0,0,0,0,0,0,0
stacksg ends
start:
mov ax,datasg
//mov dx,ax—>此行改为栈存储
-----------栈存储,
mov ss,ax
mov sp,16
-----------此内容替换上面那句:mov dx,ax
mov ds,ax
mov bx,0
mov cx,4
s0://mov ds:[40H],cx
---------栈存储
push cx //---->将外层循环的cx压入栈
---------此内容替换上面那句:mov ds:[40H],CX
mov si,0
mov cx,3
s1:mov al,[bx+si]
and al,1101 1111b//运算转换为大写
mov [bx+si],al
inc si
loop s
add bx,16
-----增加栈存储
pop cx
-----此内容为增加
loop s0
BX寄存器:基址寄存器
只有bx,bp,SI,DI可以放在[]中进行内存单元寻址,除BX以外的通用寄存器都不能放在[]里边
CX,ax,dx,ds不能放在[]中进行内存寻址
注意:[BX+BP],[SI+DI]这种寻址是错误的
bx-----si
\ /
\ /
/
idata
/
/
/
bp---- di
只能有以上的组合,[bx+si],[bp+di],[bx+di],[si+bp]
bx与bp的区别:
bx默认段地址为DS段(数据段)
bp默认段地址为SS段 (栈段地址)
es为附加段寄存器,存储附加的基段地址
mov ax,[bp]–>默认为ss:[bp] (ax)=((ss)*16+(bp))
mov ax,ds:[bp]–>指定ds段 (ax)=((ds)*16+(bp))
mov ax,es:[bp] -->指定为es段 (ax)=((es)*16+(bp))
mov ax,[bx] —>默认DS段 (ax)=((ds)*16+(bx))
mov ax,ss:[bx] -->指定SS段 (ax)=((ss)*16+(bx))
mov ax,[bp+idata]–>默认ss段 (ax)=((ss)*16+(bp)+idata)
mov ax,[bp+si] —>默认ss段 (ax)=((ss)*16+(si))
mov ax,[bp+si+idata]—>默认ss 段(ax)=((ss)*16+(si)+idata)
mov ax,cs:[bx+si] —>指定CS段 (ax)=((cs)*16+(bx)+(si))
在指令中如何确定需要处理饿数据有多长,是一个字节,还是一个字
1.看操作符mov ax,1 这为一个字的操作
2.mov al,1 这为一个字节的操作
3.当在操作指令中没有操作数,如mov ds:[0],1 这样就不知道1需要存放多大的空间内
解决方法:word ptr为一个字的空间,即2个字节16位
byte ptr为一个字节,即8位
可以写成:mov word ptr ds:[0],1 —>将1存储到偏移地址为0的空间,内存大小为一个字16位
inc word ptr[bx]—>bx指向的内存单元,是一个字,即16位2个字节
inc word ptr ds:[0] ---->偏移地址为0的那个地址单元的数据+1,操作的内存为2个字节
add word ptr [bx],1---->bx指向的内存空间的值+1,内存空间为一个字,16位
mov byte ptr ds:[0],1 --->将1存储到偏移地址为0的空间,内存大小为1个字节8位
[BX+10H+SI]=[bx].10h[si]
除法指令:DIV
DIV 寄存器===>AX/寄存器(8位)=AL…AH
===>DX AX/寄存器(16位)=AX…DX
被除数 AX DX AX—>要变成32位(dx)*10000h+(ax)
除数 8位内存/寄存器 16位内存/寄存器
商 AL AX
余数 AH DX
DIV BL =>(ax)/(bl)=(al)…(ah)
div byte ptr ds:[0]=>(ax)/((ds)*16+0)=(al)…(ah)
div byte ptr[bx+si+8]=>(ax)/((bs)*16+(bx)+(si)+8)=(al)…(ah)
div bx=>((bx)*10000h+(ax))/(bx)=(ax)…(bx) //*10000意思为变成32位的数
div word ptr es:[0]=>((dx)*10000h+(ax))/((ds)*16+0)=(ax)…(dx)
div word ptr[bx+si+8]=>((dx)*10000h+(ax))/((ds)*16+(bx)+(si)+8)=(ax)…(dx)
切记:提前在默认的寄存器中设置好被除数,且默认寄存器不作为别的用处。
案例:
利用除法指令计算100001/100=1000…1=====>186A1H/64H
需要进行16位的除法,因为被除数186A1h已经超过了16位,被除数就得用两个16位的寄存器存储
被除数:186A1H===>DX=1 AX=86A1
除数:64H===>BX=64H
商:AX
余数:DX
MOV DX,1
MOV AX,86A1H
MOV BX,64H
DIV BX
---->AX=03EB=1000D,DX=1
利用除法指令计算:1001/100=10…1==>3E9H/64H
需要使用8位的除法就可以了
被除数:3E9H===>AX=3E9H
除数:64h===>bl=64h
商:al
余数:ah
mov ax,3e9h
mov bl,64h
div bl
----->al=0A,AH=01
现实中:双字型数据的定义案例100001/100,商存放在数据单元中(除数后边的单元中)
data segment
db 100001 //可以定义1个字节的数据
dw 100 //可以定义两个字节的数据,dd 可以定义4个字节32位的数据
dw 0
data ends
mov ax,data
mov ds,ax //定义被除数的地址
mov ax,ds:[0]// AX保存的为被除数的低位16位,低位在前
mov dx,ds:[2] //偏移2个字节,被除数高位在后16位在DX
div word ptr ds:[4] //被除数在[4]中,1个字节
mov ds:[6],ax//结果自动保存在ax中,还需要转存到内存单元中。
dup功能:伪指令,用于重复数据
db 3 dup(0) -->定义了3个字节,他们都是0,等同于:db 0,0,0
db 3 dup(0,1,2)–>定义了9个字节,即3个字节重复3次,等同于:db 0,1,2,0,1,2,0,1,2
db 3 dup(‘abc’,‘ABC’)–>定义了18个字节,即6个字节重复3次,等同于:db ‘abcABCabcABCabcABC’
offset取得标号的偏移地址操作符
用法:offset 标号
例子:
assume cs:codeseg
codeseg segment
start:mov ax,offset start //相当于mov ax,001AH
ss: mov ax,offset ss //相当于mov ax,3 因为上面那条指令占三个字节
练习:将s处的指令复制到s0处:
assume cs:codesg
codesg segment
s:mov ax,bx //占两个字节
mov si,offset s
mov di,offset s0
mov ax,cs:[si] //此处两句就将s处的一条指令放入到s0指向的空字节中
mov cs:[di],ax
s0:nop //空字节占一个字节
nop
codesg ends
ends
转移指令jmp
jmp shart 标号
jmp near 标号
jmp far 标号
jmp word ptr 内存单元地址
jmp dword ptr 内存单元地址
jmp short 标号:段内转移,转移的范围为8位位移,即-128~127之间的地址范围都能跳转
也就是说,标号处的地址~跳转指令处的地址范围最多为127,往前跳可以到-128的位置,否则标号超限
assume cs:codesg
codesg segment
start:
mov ax,0
jmp short s
add ax,1
…
区域内jum到s的位置之间的地址不能大于127.
…
s:inc ax//编译器会编译成:add ax,01
jmp near ptr 标号:范围为16位距离的位移范围,范围为:-32769~32767
(ip)=(ip)+16位的地址范围
超过范围编译报错:jump out of range by 1 bytes.
jmp short和jmp near编译成机器指令后,机器指令是跳转到指令相对位置,机器码是位移地址
jmp far ptr 标号:汇编后,直接把跳转到的内存地址直接写到机器码中了,包含了CS和偏移地址IP
jmp后边可以跟16位的寄存器,直接跳到指定位置,如:
jmp ax
jmp bx
jmp word ptr:段内转移,从内存单元地址处开始存放一个字,是转移的目的偏移地址
mov ax,0123h
mov ds:[0],ax //MOV [BX],AX 相同,因为bx默认段地址是ds
jmp word ptr ds:[0] //执行后,(IP)=0123H
jmp dword ptr :段间转移,从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移量
两个字的内存单元:段地址+目标偏移地址
00 (IP)
02 (cs)
mov ax,0123h
mov ds:[0],ax //DS=0123H
mov word ptr ds:[2],0//DS高位置0,数据为012300
jmp dword ptr ds:[0] //执行后,(cs)=0,(ip)=0123h
mov ax,0123h
mov [bx],ax
mov word ptr [bx+2],0//4个字节,高地址是段地址,低地址是偏移地址
jmp dword ptr [bx] //执行后,(cs)=0,(ip)=0123h
jmp指令总结:
jmp 标号:
1.段间转移(远转移):jmp far ptr 标号
2.段内短转移:jmp short 标号,8位的位移
3.段内近转移:jmp near ptr 标号,16位的位移
jmp 寄存器
jmp bx,16位的位移
jmp 内存单元(表示跳到的地址)
1.段内转移:jmp word ptr 内存单元地址;jmp word ptr [bx],8个字节
2.段间转移:jmp dword ptr 内存单元地址;jmp dword ptr [bx],4个字节
jmp 后边不能跟立即数,如jmp 2000:0100 但是在debug中可以使用,用于直接调试
有条件跳转指令:jcxz 判断(cx)=0,则跳转到标号处执行,当(cx)不等于0时,程序向下执行
当(cx)=0时,(IP)=(IP)+8位位移,即标号处的地址~指令后的第一个字节的地址
对ip的修改范围都是-128~127,位移指的是机器码中的位移,而不是目的地址,目的地址是实际的标号位置的地址。
例:
assume 测试:codesg
codesg segment
start:
mov ax,2000h
mov ds,ax
mov bx,0
s:mov cx,[bx]
jcxz,ok
inc bx
inc bx
jmp short s
ok:mov dx,bx
mov dx,bx
mov ax,4c00h
int 21h
codesg ends
end start
例:
assume cs:codesg
codesg segment
start:
mov cx,6h //076a:0000 B90600
mov ax,10h //076A:0003 b81000
s:add ax,ax //076a:0006 03c0
loop s //076a:0008 e2fc loop 0006 //fc为补码=-4
mov ax,4c00h //076a:000a //执行loop后,会跳到这一行,然后跳转到当前地址-4就执行到s标号处。
int 21h
codesg ends
end start
注意:在设计编译时,考虑到loop在跳转时,机器码中包含的s地址,则程序段内存中的偏移地址有了严格限制,因为是绝对地址,容易引发错误,
当机器码中包含的是位移,这样无论S处的指令实际地址是多少,loop指令转移的是相对位移保持不变
这样的设计,方便了程序段在内存中浮动装配。
模块化程序设计
调用字程序 call
返回ret
mov ax,0
call s //调用s
mov ax,4c00h
int 21h
s:add ax,1
ret //返回
实质:流程转移指令,它们都是修改IP,或者同时修改CS和IP
cpu执行call指令,进行两步操作
1.将当前ip或cs和ip压入栈中 push
2.跳转到标号处执行指令 jmp near ptr 标号 16位位移范围-32768~32767,位移由编译程序在编译时算出
当前ip压栈:(sp)=(sp)-2 (ss)*16+(sp)=(ip)
ip转移:(ip)=(ip)+16位的位移
指令call far ptr 标号 实现的是段内转移
cpu执行call far ptr 标号时的操作
1.(sp)=(sp)-2
((ss)*16+(sp))=(cs)
(sp)=(sp)-2
((ss)*16+(sp))=(ip)
2.(cs)=标号所在的段地址
(ip)=标号所在的偏移地址
call far ptr标号相当于:
push cs
push ip
jmp far ptr 标号
案例:
mov ax,0
call far ptr s
…
mov ax,4c00h
int 21h
s:add ax,1
ret
转移地址在寄存器中的call指令
call 16位寄存器
功能:
(sp)=(sp)-2
((ss)*16+(sp))=(ip)
(ip)=(16位寄存器)
相当于:
push ip
jmp 16位寄存器
如:
mov ax,0
call ax
…
mov ax,4c00h
int 21h
转移地址在内存中的call指令
call word ptr 内存单元地址
例:
mov sp,10h //栈顶指针
mov ax,0123h
mov ds:[0],ax //数据段偏移位置
call word ptr ds:[0]
执行后:(ip)=0123h,(sp)=0eh -->10h-2=0eh
call dword ptr 内存单元地址
相当于:
push cs
push ip
jmp dword ptr 内存单元地址
例:
mov sp,10h //栈顶指针
mov ax,0123h
mov ds:[0],ax//低地址
mov word ptr ds:[2],0 //高地址
call dword ptr ds:[0]
执行后:(cs)=0,(ip)=0123h,(sp)=0ch—>10h-2-2=0ch
ret 的运用,相当于pop ip
assume cs:codesg,ss:stack
stack segment
db 16 dup(0)
codesg segment
mov ax,4c00h
int 21h
start:mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
codesg ends
end start
retf的运用,相当于pop ip ,pop cs,在代码中比ret多了一步push cs的操作
例:
assume cs:odesg,ss:stack
stack segment
db 16 dup(0)
stack ends
codesg segment
mov ax,4c00h
int 21h
start:mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf
codesg ends
end start
call 和ret 案例:计算2的n次方
错误案例:没有定义栈段是非常危险的
assume cs:code
code segment
start:
mov ax,1
mov cx,3
call s
mov bx,ax
mov ax,4c00h
int 21h
s:add ax,ax
loop s
ret
code ends
end start
正确程序:
assume cs:code,ss:stack
stack segment
db 8 dup(0)
db 8 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16 //定义栈段
mov ax,1000
call s
mov ax,4c00h
int 21h
s:add ax,ax
ret
code ends
end start
mul乘法指令:
格式:
mul 寄存器
mul 内存单元
8位乘法 16位乘法
被乘数(默认) AL AX
乘数 8位寄存器或内存单元 16位寄存器或内存单元
结果 AX DX(高位)AX(低位)
mul bl===>(ax)=(al)(bl)
mul byte ptr ds:[0]===>(ax)=((al)(ds)*16+0))
mul word ptr [bx+si+8]===>(ax)=((ax)*((ds)16+(bx)+(si)+8)
===>(dx)=((ax)((ds)*16+(bx)+(si)+8)
1.计算:100*10==》100和10都小于255,可以做8位乘法
mov al,100
mov bl,10
mul bl
结果:(ax)=1000(03e8h)
2.计算100*1000===》10000大于255,所以必须使用16位的乘法
mov ax,100
mov bx,10000
mul bx
结果:(dx)=000fh,(ax)=4240h
即:F4240H=1000000
案例:根据提供的N,计算N的三次方
思考:
1.将参数N存储在什么地方
2.计算得到的数值,储存在什么地方
用寄存器传递参数
1.参数放到bx中,即(bx)=N
2.子程序中用多个mul指令计算N^3
3.将结果放到dx和ax中:(dx:ax)=N^3
要求:将data段中第一组数据的3次方,保存在后面一组dword单元中
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8//一个字
dd 0,0,0,0,0,0,0,0//双字4个字节
data ends
code segment
code segment
start: mov ax,data
mov ds,ax
mov si,0 //第一组的第一个元素
mov di,16//跳过第一组,指向第二组第一个元素
;循环处理
mov cx,8//循环8次
s:mov bx,[si]//把第一个元素拿出来给bx
call cube //方法调用
mov [di],ax//把结果低16位放di
mov [di].2,dx//高位放到第二个字的高位
add si,2//跳过0,1单元
add di,4//跳过第二组第一个元素(双字4字节)
loop s//继续计算下一个
cube:
mov ax,bx //求出BX的三次方返回
mul bx
mul bx
ret
mov ax,4c00h
int 21h
code ends
end start
寄存器比较稀缺,使用内存单元批量传递数据
方案:
1.将批量数据放到内存中,然后将他们所在的内存空间的首地址放在寄存器中,传递给需要的子程序
2.对于具有批量数据的返回记过,也可以用同样的方法
编程:将data段中的字符串转为大写
assume cs:code
data segment
db ‘conversation’
data ends
code segment
start:mov ax,data//得到系统随机分配的段地址
mov ds,ax
mov si,0
mov cx,12 //循环12次
call capital
mov ax,4c00h
int 21h
capital: and byte ptr [si],1101 1111b//运算转换为大写,结果放在[si]
inc si
loop capital
ret
code ends
end start
案例:使用栈传递参数
原理:由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数
任务:计算(a-b)^3,a,b为word型数据
1.进入子程序前,参数a,b入栈
2.调用子程序,将使栈顶存放ip
3.结果:(dx:ax)=(a-b)^3
设a=3,b-1,计算(a-b)^3
mov ax,1
push ax
mov ax,3
push ax //此时栈中的数据为从下到上:1(a),3(b)
call difcube //调用子程序时,会将此行的下一行地址压入栈中
difcube:
push bp // 把bp原有的内容压入栈,防止本程序使用BP,导致原数据丢失,
//此时栈中的数据为从下到上:1(a),3(b),call的下一条指令地址,bp旧数据
mov bp,sp //将栈指针指向栈顶,即bp旧数据的位置
mov ax,[bp+4]//由于栈中的数据是2个字节,同时栈中的数据存放是高地址往低地址存放,即栈低的地址最高,所以此次+4才能取到a
sub ax,[bp+6]//将栈中的b取出,进行a-b放到ax中
mov bp,ax //A-B结果给bp
mul bp //dx:ax中放结果(a-b)*(a-b)
pop pb //将旧的bp从栈中还回去
ret 4 //===>当子程序返回时,栈中保存的call后边的一条指令的地址自动出栈,ret 4就是将指针指向开始的位置,因为现在a,b两个还没有出栈
//pop ip ;add sp,n即让栈指针指向栈低,恢复到栈的初始状态,当指针指向初始位置时,将栈内的a,b数据为无效数据。
总结:参数传递问题
1.通过寄存器,确定:资源少
2.通过内存单元
3.通过栈
子程序标准框架:
1.子程序中使用的寄存器入栈
2.子程序中使用的寄存器出栈(ret/retf)
解决方案:
在子程序的开始,将要用到的所有寄存器中的内容都保存起来,在子程序返回前再恢复
标志寄存器:按位起作用
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF
OF:Overflow 溢出 OV=1 NV=0
DF:Direction 方向 DN=1 UP=0
SF:Sigh 符号 NG=1 PL=0
ZF:zero 零值ZR=1 NZ=0
PF:parity 奇偶PE=1 PO=0
CF:carty 进位CY=1 NC=0
adc指令:带进位加法指令
格式:adc 操作对象1,操作对象2
功能:操作对象1=操作对象1+操作对象2+CF位
例:abc,ax,bx==->(ax)=(ax)+(bx)+CF
ADC指令做大数加法案例:
1.计算1E F000 1000H+20 1000 1EF0H,结果放ax(高16位),bx(次高16位),cx(低16位)中。
程序:CF需要提前置0
mov ax,1
sub ax,ax;//cf置0
mov ax,001EH
MOV BX,0FEDH
mov cx,1000H
ADD CX,1EFOH//第一次低位相加,若有进位就把cf置1
ADC BX,1000H//高位相加
ADC AX,0020H
128位的数据相加
671E 0F04 54F6 887A 0A8E 78E6 0A8F 0A452
- 0452 58F5 78E6 0A8E 8B7A 54F6 0EF04 0571
功能分析:128位需要8个字单元,由低地址单元到高地址单元,依次存放由低到高的各个字
ds:si指向存储第一个数的内存空间
ds:di指向存储第二个数的内存空间
运算结果存储在第一个数的存储空间中
data segment
dw 0A452,0A8F,78E6,0A8E,887A,54F6,0F04,671E
dw 0571,0FE04,54F6,8B7A,0A8E,78E6,58F5,0452
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
call add128
mov ax,4c00h
int 21h
add128:
push ax
push cx
push si
push di
sub ax,ax
s:mov ax,[si]
adc ax,[di]
mov [si],ax
inc si
inc si
inc di
inc di
loop s
pop di
pop si
pop cx
pop ax
ret
code ends
end start
注意:sub ax,ax不能替换成mov ax,0 ,其目的是将CF置为0,只有算术运算后才会影响次标志位
两个inc di不能替换成add di,2,虽然其结果相同,但是inc的结果不会参与CF标志位的变化,但是add会参与
sbb指令:带借位减法指令
格式:sbb 操作数1,操作数2
功能:
操作对象1=操作对象1-操作对象2
与sub的区别,利用CF位上记录的借位值
例如:
sbb ax,bx===>(ax)=(ax)-(bx)-CF
案例:对任意大的数做减法运算
计算003E 1000H-0020 2000H
程序:
mov bx,1000H
mov ax,003EH
sub bx,2000H
sbb ax,0020H
cmp 无符号数比较与标志位取值
cmp 操作对象,操作对象2
功能:操作对象1-操作对象2=标志位寄存器的值变化
cmp ax,bx
(ax)=(bx) ZF=1
(ax)≠(bx) ZF=0
(ax)>(bx) CF=0且ZF=0
(ax)<(bx) CF=1
(ax)≥(bx) CF=0
(ax)≤(bx) CF=1或 ZF=1
比较指令的设计思路:通过做减法运算影响标志寄存器,标志寄存器的相关位的取值,体现比较的结果
JXX 标号转移指令,根据标志位进行判断,符号就转移
根据单个
je/jz 相等/结果为0 ZF=1
jne/jnz 不等/结果不为0 ZF=0
js 结果为负 SF=1
JNS 结果非负 SF=0
JO 结果溢出 OF=1
JNO 结果非溢出 OF=0
JP 奇偶位为1 PF=1
JNP 奇偶位不为1 PF=0
JB/Jnae/jc 低于/不高于等于/有借位 CF=1
JNB/jae/jnc 不低于/高于等于/无错位 CF=0
注:j=jump e=equal n=not b=below a=above l=less g=greater s=sign c=carry p=parity o=overflow z=zero
根据无符号数比较结果进行转移的指令
jb/jnae/jc 低于则转移 CF=1
jnb/jnae/jnc 低于则转移 CF=0
jna/jbe 不高于则转移 CF=1或ZF=1
ja/jbe 不高于则转移 CF=1且ZF=0
根据有符号数比较结果进行转移的指令
jl/jnge 小于则转移 SF=1且OF=0
jnl/jge 不小于转移 SF=0且OF=0
jle/jng 小于等于则转移 SF=0或OF=1
jnle/jg 不小于等于则转移 SF=1且OF=1
运用案例:
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
2.如果ax=0,则(ax)=(ax)+1
;ax获取值
add ax,0//目的是:当[ax]=0,结果也为0,ZF=1,否则结果不为0,ZF=0
jnz s //AZ=0时跳转
inc ax
s:…
条件转移指令案例:
1.双分枝结构
cmp ah,bh
je s //相等转到S
add ah,bh
jmp short ok
s:add ah ,ah
ok:ret
if(a==b){
a=a+b;
}eles{
a=a+b}
2.给出数据,求字节个数
data segment
db 8,11,8,1,8,5,63,38
data ends
a.求数值为8的个数
b.大于8的字节个数
c.小于8的字节个数
编程思路:
a.求数值为8的个数
初始设置(ax)=0,然后用循环依次比较每个字节的值,找到一个和8相等的数就将ax+1.
code segment
start:mov ax,data
mov ds,ax
mov bx,0 //第一个元素的偏移量
mov ax,0 //计数
mov cx,8 //循环次数
s:cmp byte ptr [bx],8//用8进行比对
jne next //不相同就跳转,不相同就继续
inc ax// 相同就让ax+1
next:inc bx //地址+1
loop s
mov ax,4c00h
int 21h
code ends
b.大于8的字节个数
code segment
start: mov ax,data
mov ds,ax
mov bx,0
mov ax,0
s:cmp byte str [bx],8
jna next
inc ax
next:inc bx
loop s
mov ax,4c00h
int 21h
将data段中的第一个字符串复制到它后面的空间中
data segment
db ‘welcome to masm!’
db 16 dup(0)
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8//16个字节,使用双字节进行存储,使用8个循环
s:mov ax,[si]
mov [di],ax
add si,2
add di,2
loop s
mov ax,4c00h
int 21h
code ends
end start
以上可以使用DF标志位进行简化代码
DF-方向标志位
功能:
在串处理指令中,控制每次操作后si,di的增减
DF=0,每次操作后si,di递增
DF=1,每次操作后si,di递减
串传送指令:
movsb 按照一个字节8位进行传送
功能:
1.((es)*16+(di))=((ds)*16+(si))
2.如果DF=0,则(si)=(si)+1
(di)=(di)+1
如果DF=1,则(si)=(si)-1
(di)=(di)-1
movsw 按照以一个字16位进行传送
1.((es)*16+(di))=((ds)*16+(si))
2.如果DF=0,则(si)=(si)+2
(di)=(di)+2
如果DF=1,则(si)=(si)-2
(di)=(di)-2
对DF位进行设置的指令
cld指令,将DF位设为0 (clear)
std指令,将DF位设为1 (setup)
优化:将data段中的第一个字符串复制到它后面的空间中
data segment
db ‘welcome to masm!’
db 16 dup(0)
data ends
code segment
start: mov ax,data
mov ds,ax
mov es,ax//设置副地址段,指向ds的同一片空间
mov si,0
mov di,16
cld //非常重要,用于设置DF=0,让si和di在使用movsb时自增
mov cx,16//16个字节,使用一个字节进行存储,使用16个循环
s:movsb//-->代替了这几个操作:mov ax,[si];mov [di],ax; add si,2;add di,2
loop s
mov ax,4c00h
int 21h
code ends
end start
rep指令重复指令.
rep movsb===>s:movsb;loop s
rep movsw===>s:movsw;loop s
优化:将data段中的第一个字符串复制到它后面的空间中
data segment
db ‘welcome to masm!’
db 16 dup(0)
data ends
code segment
start: mov ax,data
mov ds,ax
mov es,ax//设置副加段,指向ds的同一片空间
mov si,0
mov di,16
cld //非常重要,用于设置DF=0,让si和di在使用movsb时自增
mov cx,16//16个字节,使用一个字节进行存储,使用16个循环
rep movsb//代替如下两行
;s:movsb//-->代替了这几个操作:mov ax,[si];mov [di],ax; add si,2;add di,2
;loop s
mov ax,4c00h
int 21h
code ends
end start
运用实例:
用串传送指令,将F000段中的最后16个字符复制到data段中
data segment
db 16 dup(0)
data ends
code segment
start:mov ax,0F000H
mov ds,ax //把数据段赋值给段地址寄存器
mov si,0FFFFH //源寄存器保存为F0000段中的最后一个地址,指向最后一个元素
mov ax,data //获取需要存储的首地址
mov es,ax //首地址给副段寄存器
mov di,15 //指向源地址的最后一个元素
mov cx,16 //循环16次
std//将DF=1,按照si,di减1的操作,
rep movsb按照字节循环16次
mov ax,4c00h
int 21h
code ends
end start