6.2 在代码段中使用栈
完成下面的程序,利用栈,将程序中定义的数据逆序存放。
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0cbah,0987h
?
codesg ends
end
程序思路大致如下。
程序运行时,定义的数据存放在cs:0~cs:F单元中,共8个字单元。依次将这8个字单元中的数据入栈,然后再依次出栈到这8个字单元中,从而实现数据的逆序存放。
问题是,我们首先要有一段可当作栈的内存空间。如前所述,这段空间应该由系统来分配。可以在程序中通过定义数据来取得一段空间,然后将这段空间当作栈空间使用。程序如下。
程序6.3
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0
;用dw定义16个字型数据,在程序加载后,将取得16个字的
;内存空间,存放16个数据。在后面的程序中将这段
;空间当作栈来使用
start:mov ax,cx
mov ss,ax
mov sp,30h ;将设置栈顶ss:sp指向cs:30
mov bx,0
mov cx,8
s: push cs:[bx]
add bx,2
loop s ;以上将代码段0~15单元中的8个字型数据依次入栈
mov bx,0
mov cx,8
s0: pop cs:[bx]
add bx,2
loop s0 ;以上依次出栈8个字型数据到代码段0~15单元中
mov ax,4c00h
int 21h
codesg ends
end start ;指明程序的入口在start处
注意程序6.3中的指令
mov ax,cs
mov ss,ax
mov sp,30h
我们要将cs:10~cs:2F的内存空间当作栈来用,初始状态下栈为空,所以ss:sp要指向栈底,则设置ss:sp指向cs:30。如果对这点还有疑惑,建议回头认真复习一下第3章。
在代码段中定义了16个字型数据,它们的数值都是0。这16个字型数据的值是多少,对程序来说没有意义。我们用dw定义16个数据,即在程序中写入了16个字型数据,而程序在加载后,将用32个字节的内存空间来存放它们。这段内存空间是我们所需要的,程序将它用作栈空间。可见,我们定义这些数据的最终目的是,通过它们取得一定容量的内存空间。所以我们在描述dw的作用时,可以说用它定义数据,也可以说用它开辟内存空间。比如对于:
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
可以说,定义了8个字型数据,也可以说,开辟了8个字的内存空间,这段空间中每个字单元的数据依次是:0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h。因为它们最终的效果是一样的。
检测点 6.1
一、在下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,完成程序:
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cvah,0987h
start:mov ax,0
mov ds,ax
mov bx,0
mov cx,8
s: mov ax,[bx]
——————
add bx,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
二、下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,数据的传送用栈来进行。栈空间设置在程序内。完成程序:
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cvah,0987h
dw 0,0,0,0,0,0,0,0,0,0 ;10个字单元用作栈空间
start:mov ax,____
mov ss,ax
mov sp,____
mov ax,0
mov ds,ax
mov bx,0
mov cx,8
s: push [bx]
________
add bx,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
完成检测点之后再进行后续课程学习
6.3 将数据、代码、栈放入不同的段
在前面的内容中,我们在程序中用到了数据和栈,将数据、栈和代码都放到了一个段里面。我们在编程的时候要注意何处是数据,何处是栈,何处是代码。这样作显然有两个问题:
- 把它们放到一个段中使程序显得混乱;
- 前面程序中处理的数据很少,用到的栈空间也小,加上没有多长的代码,放到一个段里没有问题。但如果数据、栈和代码需要的空间超过64KB,就不能放在一个段中(一个段的容量不能大于64KB,是我们在学习中所用的8086模式的限制,并不是所有的处理器都这样)。
所以,应该考虑用多个段来存放数据、代码和栈。
怎样做呢?我们用和定义代码段一样的方法来定义多个段,然后在这些段里面定义需要的数据,或通过定义数据来取得栈空间。具体的做法如下面的程序所示,这个程序实现了和程序6.3一样的功能,不同之处在于它将数据、栈和代码放到了不同的段中。
程序6.4
assume cs:code,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
code segment
start: mov ax,stack
mov ss,ax
mov sp,20h ;设置栈顶ss:sp指向stack:20
mov ax,data
mov ds,ax ;ds指向data段
mov bx,0 ;ds:bx指向data段中的第一个单元
mov cx,8
s: push [bx]
add bx,2
loop s ;以上将data段中的0~15单元中的8个字型数据依次入栈
mov bx,0
mov cx,8
s0: pop [bx]
add bx,2
loop s0 ;以上依次出栈8个字型数据到data段的0~15单元中
mov ax,4c00h
int 21h
code ends
end start
下面对程序6.4做出说明。
(1)定义多个段的方法
这点,我们从程序中可明显地看出,定义一个段的方法和前面所讲的定义代码段和方法没有区别,只是对于不同的段,要有不同的段名。
(2)对段地址的引用
现在,程序中有多个段了,如何访问段中的数据呢?当然是通过地址,而地址是分为两部分的,即段地址和偏移地址。如何指明要访问的数据的段地址呢?在程序用,段名就相当于一个标号,它代码了段地址。所以指令“mov ax,data”的含义就是将名称为“data”的段的段地址送入ax。一个段中的数据和段地址可由段名代码,偏移地址就要看它在段中的位置了。程序中“data”段中的数据“0abch”的地址就是:data:6。要将它送入bx中,就要用如下的代码:
mov ax,data
mov ds,ax
mov bx,ds:[6]
我们不能用下面的指令:
mov ds,data
mov bx,ds:[6]
其中指令“mov ds,data”是错误的,因为8086CPU不允许将一个数值直接送入段寄存器中。程序中对段名的引用,如指令“mov ds,data”中的“data”,将被编译器处理为一个表示段地址的数值。
**(3)“代码段”、“数据段”、“栈段”完全是我们的安排
现在,我们以一个具体的程序来再次探讨一下所谓的“代码段”、“数据段”、“栈段”。在汇编程序中,可以定义许多的段,比如在程序6.4中,定义了3个段,“code”、“data”、“stack”。我们可以分别安排它们存放代码、数据和栈。那么我们如何让CPU按照我们的这种安排来执行这个程序呢?下面来看看源程序中对这3个段所做的处理。
- 我们在源程序中为这3个段起了具有含义的名称,用来访数据的段我们将其命名为“data”,用来放代码的段我们将命名为“code”,用作栈空间的段命名为“stack”。
这样命名了之后,CPU是否就去执行“code”段中的内容,处理“data”段中的数据,将“stack”当作栈了呢?
当然不是,我们这样命名,仅仅是为了使程序便于阅读。这些名称同“start”、“s”、“s0”等标号一样,仅在源程序中存在,CPU并不知道它们。
- 我们在源程序中伪指令“assume cs:code,ds:data,ss:stack”将cs、ds和ss分别和code、data、stack段相连。这样做了之后,CPU是否就会将cs指向code,ds指向data,ss指向stack,从而按照我们的意图来处理这些段呢?
当然也不是,要知道assume是伪指令,是由编译器执行的,也是仅在源程序中存在的信息,CPU并不知道它们。我们不必深究assume的作用,只要知道需要用它将你定义的具有一定用途的段和相关的寄存器联系起来就可以了。
- 若要CPU按照我们的安排行事,就要用机器指令控制它,源程序的汇编指令是CPU要执行的内容。CPU如何知道去执行它们呢?我们的源程序的最后用“end start”说明了程序的入口,这个入口将被写入可执行文件的描述信息,可执行文件中的程序被加载入内存后,CPU的CS:IP被设置指向这个入口,从而开始执行程序中的第一条指令。标号“start”在“code”段中,这样CPU就将code段的内容当作指令来执行了。我们在code段中,使用命令:
mov ax,stack
mov ss,ax
mov sp,20h
设置ss指向stack,设置ss:sp指向stack:20,CPU执行这些指令后,将把stack段当做栈空间来用。CPU若要访问data段中的数据,则可用ds指向data段,用其他寄存器(如bx)来存放data段中数据的偏移地址。
总之,CPU到底如何处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间,完全是靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的。完全可以将程序6.4写成下面的样子,实现同样的功能。
assume cs:b,ds:a,ss:c
a segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
a ends
c segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
c ends
code a
d: mov ax,c
mov ss,ax
mov sp,20h ;设置栈顶ss:sp指向stack:20
mov ax,a
mov ds,ax ;ds指向data段
mov bx,0 ;ds:bx指向data段中的第一个单元
mov cx,8
s: push [bx]
add bx,2
loop s ;以上将data段中的0~15单元中的8个字型数据依次入栈
mov bx,0
mov cx,8
s0: pop [bx]
add bx,2
loop s0 ;以上依次出栈8个字型数据到data段的0~15单元中
mov ax,4c00h
int 21h
b ends
end d