3.1内存种字的存储

1.一个内存单元的大小是1B,任何两个地址连续的内存单元,N号单元和N+1号单元,可以将他们看成两个内存单元,也可以看出一个地址为N的字单元中的高位字节单元和低位字节单元

image-20230406150702374

**两个十六进制位是一个字节

1
2
3
4
0地址单元中存放的字节型数据:20H
0地址单元中存放的字型数据:4E20H

后面的是高位

3.2DS和[address]

1.CPU要读取一个内存单元的时候,必须先给出 这个单元的地址;

2.在8086CPU中,内存地址由段地址和偏移地址组成

3.8086CPU中由一个DS寄存器,通常用来存放要访问的数据的段地址

1
CS寄存器就是当前要读取指令的地址。

4.例子:我们要读取1000H单元的内容,可以用如下的程序进行

1
2
3
mov bx,1000H	//这里就是在bx寄存器中放入了1000H这个数据
mov ds,bx //把bx寄存器中的1000H数据放到DS寄存器中
mov al,[0] //[0],这里是偏移地址的意思,CPU在用到偏移地址的时候,会先去找到段地址,在第二部已经把段地址设置成1000H了,所以这句话的意思就是把地址位1000:0的数据放入al寄存器中【这个al的寄存器是ax下的一个low低位寄存器】
1
2
3
注意:
这里不能mov dx,1000H
只能通过通用寄存器导过来

5.mov指令的功能

1
2
3
4
5
6
7
8
9
1.将数据之间送入寄存器;【不能直接放入段寄存器】
mov ax,2
2.将一个寄存器中的内容送入另一个寄存器中
mov ax,bx
【通过这种方式来送入段寄存器】
3.将一个内存单元中的内容送入一个寄存器
mov ax,[0]
某个内存单元中的内容,需要用DS[address]指向这个地址后,才会送入另外一个寄存器
也就是说,在有[]的时候是去直接读取DS和address[偏移地址]找到这个地址里面的内容,放入ax寄存器中
1
2
3
mov ax,bx ==>寄存器寻址 
mov ax,1000H ==>立即寻址
mov ax,[0] ==>直接寻址
1
2
3
4
5
在修改DS段寄存器中的内容的时候是:利用了通用寄存器结合mov实现的

但是修改CS段的寄存器的时候是直接使用jmp命令修改的
jmp DS:IP --> 这个就是DS和IP一起修改
jmp ax -->这个就是DS不变,IP变成AX寄存器中的值

6.写几条指令,将al中的数据送入内存单元10000H?

1
2
3
4
mov BX,1000H
mov DS,BX
mov [0],AL
这里是把数据放入某单元

3.3字的传送

1.因为8086CPU是16位结构,有16根数据线,所以一次性传入16位的数据,也就是一次性传送一个字

1
2
3
4
5
比如:
mov bx,1000H
mov ds,bx //前面俩个是为了确定段地址
mov ax,[0] //1000:0处的字型数据送入ax中
mov [],cx //cx中16位数据送到1000:0处

小结一下【实验】P14

首先CS:IP 和 DS[偏移地址]是密不可分的【好像有点奇怪,现在姑且这样理解】

CS:IP确定了指向命令的地址 【在我看来就是地址总线】

1
好像也不是很对,emmmm,反正就是说在CS:IP的地址中的内容可以放入命令,然后进行执行
1
2
首先在地址中写入要执行命令的汇编指令,
然后用CS:IP指向这些地址,在运行的时候,这些地址里面的内容就会指向命令

image-20230406160947921

通过运行CS:IP地址中的内容就可以去改变DS中的内容,然后读取DS地址中的内容放入或者运算入AX BX CX DX通用寄存器中

DS确定了读取数据的地址 【在我看来就是数据总线】

1
2
反正就是说运行的命令,都是在CS:IP地址中的内容,如果CS:IP中的某些命令需要用到DS,则就会去调用DS[]地址中的值。
差不多就是这个意思

当通用寄存器用的是X的时候【16位的时候】就需要把该地址和该地址+1的内容当作字来看

但是如果使用的是L/H的时候,就需要当作字节来看

1
2
3
4
5
6
因为,这个传入的内容不能大于放入的寄存器
mov ax,[0] //这样就是当作字来看,因为ax寄存器中可以存16位【一个字】
mov al,[0] //这样就要当作字节来看,因为al寄存器中只能放入8位【一个字节】

寄存器有多大,就放多少,在选中的该地址往后加就好了
以寄存器为标准,内存按照寄存器的大小对寄存器放入值

3.4mov,add, sub指令

1.mov

1
2
3
4
5
6
7
8
9
10
11
12
13
14
已知:
mov 寄存器,数据
mov ax,100
mov 寄存器,寄存器
mov ax,bx
mov 寄存器,内存单元 //内存单元这里就是DS[address]指向的位置
mov ax,[0]
mov 内存单元,寄存器
mov [0],ax
mov 段寄存器,寄存器
mov ds,ax
新的:
mov 寄存器,段寄存器
mov ax,ds

2.add【➕】和sub【➖】

1
2
3
4
5
6
7
8
9
10
11
12
13
add 寄存器,数据
add ax,3
add 寄存器,寄存器
add ax,bx
add 寄存器,内存单元
add ax,[0]
add 内存单元,寄存器
add [0],ax
注意:!!!
add不能对段寄存器进行操作
比如:add ds,ax ==> 这样是错误的!!【段寄存器不是这么用的,所以没有设置这个功能】

##然后sub和add完全一样

3.5数据段

1.前面讲过,对于8086PC机,我们可以根据需要将一组内存单元定义为一个段(这个段,可以是代码段,也可以是数据段)

2.我们可以将一组长度为N(N<=64K),地址连续,起始地址是16的倍数的内存单元当作专门存储数据的内存空间,这个空间就是定义的数据段

3.比如我们用123BOH~123B9H这段空间来存放数据:

1
2
段地址:123BH  ==>这里会经过加工厂*16=123BOH
长度是10字节【123B0~123B9有10个存储单元【一个存储单元就是1B】】

4.如何访问你数据段的中数据?

1
题目1:我们将123BOH~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元中的数据
1
2
3
4
5
6
7
解:
mov ax,123BH
mov ds,ax
mov al,0 //因为数题目是前三个单元中的数据,每一个存储单元的大小是1B也就是8位,所以这里需要使用低位的al 而不是 使用ax,如果使用ax的话,就会把数据放入字中【16位】这样所求得的就不是前三个单元中的数据的累加,而是前三个字中数据的累加
add al,[0] //计算机都是从0开始哦
add al,[1]
add al,[2]
1
题目2:我们将123BOH~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个字型中的数据
1
2
3
4
5
这里就是把al改成ax就好啦,当然偏移地址也要累加2【懂的都懂,不懂的就别学了】
add ax,[0] //为了放入ax中其实这里是[0]放入低位[1]放入高
add ax,[2]
add ax,[4]
注意:一个字型数据占2个单元,所以偏移地址是0,2,4

3.1~3.5小结一下

1**.字在内存中存储的时候,要用俩个地址连续的内存单元来存放,字的低位字节【al】存放在低地址单元【0】中,高位字节【ah】存放在高地址单元【1】中

2.用mov指令要访问内存单元,可以在mov指令中只给处单元的偏移地址,此时,段地址默认在DS寄存器中

3.[address]表示一个偏移地址位address的内存单元

4.在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器,低地址单元和低8位寄存器相对应

5.mov add sub 是具有两个操作对象的指令。jmp是具有一个操作对象的指令

6.可以根据自己的推测,在debug中实验指令的新格式

1
根据弹幕: mov ax,1000:[0]  ==>  这样不error的【我也不确定】

3.6栈

1.我们研究栈的角度:

1
2
3
首先:栈是内存空间的一部分

栈是一种具有特殊的访问方式的存储空间,它的特殊性就在于,最后进入这个空间的数据,最先出去【迟到早退】

2.栈有俩个基本的操作:入栈和出栈

1
2
入栈:将一个新的元素放到栈顶
出栈:从栈顶取出一个元素

3.栈顶的元素总是最后入栈,需要出栈的时候,又是最先被从栈中取出

4.栈的操作规则:LIFO

1
2
Last In First Out
后进先出

3.7CPU提供的栈机制

1.如今的CPU中都有栈的设计

2.8086CPU提供相关的指令来以栈的方式访问内存空间

3.这意味着,我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用

1
只是我们当作栈来使用,sb计算机只会0和1

4.8086CPU提供入栈和出栈指令:

1
2
3
4
5
最基本:
PUSH:(入栈)【入就入到最底部】
POP(出栈)【出就是从顶部出】
push ax:将寄存器ax中的数据送入栈中
pop ax:从栈顶取出数据放入ax

5.8086CPU的入栈和出栈操作都是以字【16位】为单位进行【这个是因为8086CPU内部是以16位为单位运算的,现在都是32/64位咯】

6.CPU如何知道一段内存空间被当作栈使用?

1
2
3
4
首先我们知道
段寄存器CI:IP所指向的就是指令
段寄存器DS所指向的就是数据
所以,以此类推,肯定有一个栈寄存器
1
2
3
4
在8086CPU中,有俩个寄存器:
段寄存器SS :存放栈顶的段地址
寄存器SP : 存放栈顶的偏移地址
任意时刻,SS:SP指向栈顶元素
1
这里我们要注意的是:栈顶不是某个栈的顶部,而是某栈存放了数据,然后栈顶就是最后放入数据的位置

image-20230406173524469

7.在执行PUSH和POP的时候,如何知道那个单元是栈顶单元

1
在指向PUSH和POP的时候,栈顶的位置是动态变化的,那么它是如何知道的呢?

push ax

1
2
3
4
这个步骤在计算机中其实是两步
1.SP=SP-2 //这里是-2而不是+2的原因是:在push的时候,数据会首先放入栈的最底部【也就是偏移地址最大的时候,高地址】
//这个就类似于IP=IP+所读指令长度
2.将ax中的内容送入SS:SP指向的内存单元出【字】,SS:SP此时指向新栈顶

pop ax

1
2
3
4
5
6
就是push的逆运算
1.将SS:SP指向的字中的内容放入ax后
2.SP=SP+2

在使用POP之后,取出来原来地址上的内容还是存在的,【可以理解成,SP变了,只是把内容copy出去了】
内存中的数据是不会被删除,只能一次一次的被覆盖被覆盖

8.问题:如果我们将10000H~1000FH这段空间当作栈,初始状态栈是空的,此时SS=1000H,那SP=?

1
2
3
4
5
当然是不是000FH啦
**是最高单元的下一个地址
SP=0010
因为只有指向最高单元的下一个地址
在push 的时候,整出俩个地址才会对应上哦

image-20230406201341990

换个角度看看:

1
2
3
1.任何时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就步存在栈顶元素
2.所以在栈为空的时候,SS:SP就只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2【这里是字单元,如过是字节的话,就是+1】
3.栈最底部子单元地址是1000:000E,所以栈为空的时候,SP=0010H

3.8栈顶超界的问题

1.SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈的时候找到栈顶。

2.但是,如何能够保证在入栈和出栈的时候,栈顶不会超出栈空间

3.当栈满的时候在使用PUSH指令入栈,栈空的时候在使用POP指令出栈,都会发生栈顶出界的问题

1
2
3
哟~出现了溢出
向上的溢出--push
向下的溢出--pop

4.栈顶越界是危险的

1
2
3
因为我们既然将一段空间安排为栈,那么在占空间之外的空间里面很可能存放了具有其他用途的数据,代码。这些数据,代码可能是我们自己程序中的,也可能是贝格程序中的(毕竟一个计算机系统并不是只有我们自己程序在运行)

相当于在溢出的时候,可以用PUSH或POP把栈之外的内容给搞出来,然后就会出现一些泄露或者,覆盖掉其他的东西。【类似吧】

5.但是由于我们在入栈和出栈时候的不小心(故意)加上一些其他工具【乱说的,可能可以把这些内容收集起来,就可以无限可能】

6.8086CPU不保证对栈的操作不会越界

1
2
3
8086CPU只知道栈顶在何处
因为栈的大小是我们自己定义的,CPU不知道我们定义了多大,不知上线和下线,所以CPU就会很SB的以为还在栈里面【但是这个时候已经跑出去了】
CPU只知道要执行的栈顶在何处,但是不知道要栈空间的大小是多少

7.总的一句话,我要想办法使他溢出【哈哈】

3.9PUSH和POP指令

1.首先PUSH和POP是可以在寄存器和内存之间传送数据的

1
1.栈空间是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间

2.看看POP和PUSH指令的格式把

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
通用寄存器:
1.PUSH 寄存器:将一个寄存器中的数据入栈
2.POP 寄存器:出栈,用一个寄存器接受出栈的数据
段寄存器:
1.PUSH 段寄存器:将一个段寄存器中的数据入栈
2.POP 段寄存器:出栈,用一个段寄存器接受出栈的数据
【也就是说,PUSH和POP可以对段寄存器使用】
【补充,ADD和SUB不能对段寄存器使用】
内存单元:
1.PUSH 内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)
2.POP内存单元:出栈,用一个内存字单元接收出栈的数据
PUSH [0] //字型的数据
POP [2]

指令执行的时候,CPU要知道内存单元的地址,可以在PUSH POP 指令中给处内存单元的偏移地址,段地址在指令执行的时候,CPU从DS取得

数据的段地址在ds中获得 用[]来偏移地址
代码的段地址在cs中获得 用ip来偏移地址
栈的段地址在ss中获得 用sp来偏移地址

问题1:

1
2
编程将10000H~1000FH这段空间当作栈,初始状态是空的,将AX,BX,DS中的数据入栈
入栈就是push

解答

1
2
3
4
5
6
7
8
9
1.mov ax,1000H

2.mov ss,ax //这里之前说过,段地址不能直接用数据,必须得用通用寄存器来过度。所以这里先把1000H存入ax,后放入ss

3.mov sp,0010H //这个sp不是段寄存器,段寄存器【CS,DS,SS,ES】.这里为什么是0010H呢?因为栈是到1000FH所以要指向这个的下一个

4.push ax
5.push bx
6.push ds

问题2:

1
2
3
4
5
1.将10000H~1000FH这段空间当作栈,初始状态是空的
2.设置AX=001AH,BX=001BH
3.将AX BX 中的数据入栈
4.然后将AX BX 清零
5.从栈中恢复AX BX 原来的内容

解答:

1
2
3
4
这个很简单啦,要注意的是
最后进入的最先出来的
清零是
sub ax,ax或者mov ax,0 或者xor ax,ax[这个是异或]

问题3

1
2
3
1.将10000H~1000FH这段空间当作栈,初始状态是空的
2.设置AX=002AH,BX=002BX
3.利用栈,交换AX和BX中的数据

解答

1
2
3
4
push ax
push bx
pop ax //把原来bx的值放入ax
pop bx //把原来ax的值放入bx

问题4

1
2
3
4
5
6
如果要在10000H处写入字型数据2266H这么搞?
mov ax,1000H
mov ds,ax // 这步就是要让ds段寄存器指向1000H
mov ax,2266H
mov [0],ax //把2266H的值放入10000H
相当于100000H是66;100001H是22
1
2
3
4
5
6
7
8
现在进阶
1.________
2.________
3.________
mov ax,2266H
push ax

完成代码,实现上诉的结果

解:

1
2
3
4
5
6
7
8
9
10
11
分析一波,我们看哈,最后俩个是:
mov ax,2266H
push ax
读一下:这里是把2266H放入ax寄存器中,然后通过栈的PUSH把ax寄存器中的内容push进10000H处
所以:我们需要在这个之前需要把栈的指针指向10000H处
所以:
1.mov ax,1000H
2.mov ss,ax
3.mov sp,2 //这里要想明白:sp的步骤是什么先-2后入栈,这里后面push ax,指针是在10002H的位置,然后push ax 也就是说在10001H的位置会存入22,10000H的位置会存入66

这个指针是sp-2是往上【低】走

总的分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
我想问一下:我这样的分析是不是对的
mov ax,1000H
mov ds,ax
mov ax,2266H
mov [0],ax

如果cpu运行了这段命令,然后在地址10000H处和10001H处10002H处的内容是什么?
【我本身想实验的,但是在win11下没有debuf,然后虚拟机我没有下xp,由于急于想知道答案所以没有下】【附加上我不知道还有什么调试器可以用-目前只知道debug】
我感觉像是:,然后在10000H中存入的是66 在10001H中是22
【因为电子书--王爽的汇编在3.1~3.5的小结里面有字在内存中存储的时候,需要用2个连续的内存单元来存放,字的低位字节存放在低地址中[这里我认为是al]高位字节放入高地址中[这个我认为是ah]】

然后在3.9中的问题3.10中有一个

mov ax,1000H
mov ss,ax
mov ap,2
mov ax,2266H
push ax
这样的代码是等价于上面的代码【书上这么说的】

分析这个命令:栈的段地址是1000H,然后偏移地址是0002,所以组合在一起这个地址是10002H
然后在push,ax的时候【这个ax里面是2266】
在高地址中存入的是22【10001H】低地址存入的是66【10000H】

问题得出的结论

1.PUSH和POP实质上是一种内存传送指令,可以在寄存器和内存之间传送数据,和mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的

2.同时,push和pop指令还会改变sp的内容

3.10栈段

1.段–>就是一组内存单元

2.我们可以将长度为N(<=64K)的一组地址连续,起始地址为16倍数的内存单元,当作栈来用,从而定义了一个栈段

3.将内存当作栈段,只是我们在编程时候的一种安排,CPU并不会知道这个空间,CPU只知道SS:SP。

问题1

如果我们将10000H~1FFFFH这段当作栈段是,初始状态是空的,此时SS=1000H那么SP是多少呢?

解:

1
2
3
猜测的是:超出了啊不知道了FFFF已经是64KB了

这里emmm SP=0

换一个角度看看

1
任何时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地址是栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:FFFE,所以栈空的时候,SP=0000H

栈的作用

1
2
就是拿来临时保存东西的
大概就是用来封装函数

总结

1.我们可以将一段内存定义为一个段,用一个段地址来指示段,用偏移地址访问段内的单元。【这完全是我们的安排】

2.我们可以用一个段存放数据,将它定义成“数据段”

1
2
3
DS:[]
CPU就将我们定义的数据段中的内容当作数据来访问
比如mov add sub

我们可以用一个段存放代码,将它定义成“代码段”

1
2
CS:IP
CPU就将执行我们定义的代码段中的指令

我们可以用一个段当作 栈,将它定义成“栈段”

1
2
SS:SP
CPU在需要进行栈操作的时候,执行PUSH POP 指令的时候,就将我们定义的栈段当作栈空间来使用