包含多个段的程序
在操作系统中,合法的通过操作系统取得的空间都是安全的,因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间相冲突,在操作系统允许的情况下,程序可以取得任意容量的空间。
程序取得所需的空间的方法有两种:
- 在加载程序的时候对程序分配
- 程序在执行的过程中向系统申请
我们若要一个程序在被加载的时候取得所需的空间,则必须要在源程序中做出说明,我们通过在源代码中定义段来进行内存空间的获取。
在代码段中使用数据
具体做法,如下代码:
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
mov bx,0
mov ax,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end
解释:
- dw的含义是定义字型数据,在这里,使用dw定义了8个字型数据,它们所占的内存空间的大小为16个字节
- 由于这8个数据在代码段中,程序在运行的时候CS中存放代码段的段地址,所以可以从CS中得到它们的段地址。它们的偏移地址是多少?因为dw定义的数据处于代码段的最开始,所以偏移地址为0,这8个数据就在代码段的偏移0,2,4,6,8,A,C,E处。程序运行时,它们的地址就是CS:0,CS:2,CS:4,CS:8,CS:A,CS:C,CS:E
- 程序中,用bx存放加2递增的偏移地址,用循环来进行累加,在循环开始前,设置(bx)=0,cs:bx指向第一个数据所在的字单元,每次循环中(bx)=(bx)+2.cs:bx指向下一个数据所在的字单元
将程序编译连接为可执行文件,用debug加载查看一下,如图所示:
为什么没有看见程序中的指令?实际上用U命令查看的也是程序中的内容,只不过不是源代码中的汇编指令所对应的机器码,而是源程序中,在汇编指令前面,用dw定义的数据,实际上,在程序中,有一个代码段,在代码段中,前面的16个字节是用dw定义的数据,从16个字节开始才是汇编指令所对应的机器码
可以从076A:0010查看程序中要执行的机器指令,如图所示:
怎么执行程序中的指令?用debug加载后,可以将IP设置为10h,从而使CS:IP指向程序中的第一个指令,然后再用t命令,p命令,或者是g命令执行。
这样一来,我们旧必须用Debug来执行程序,程序编译,连接成可执行文件后,在系统中国直接运行可能会出现问题,因为程序的入口处不是我们所希望执行的指令,我们可以在源代码中指明程序的入口所在,具体做法如下:
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
start: mov bx,0
mov ax,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end start
这里,我们要再次讨论end的作用,end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方,程序中我们用end指令指明了程序的入口在标号start处。
如何知道哪一条指令是程序的第一条要执行的指令?这一点,是由可执行文件中的描述信息指明的。我们知道可执行文件由描述信息和程序组成,程序来自于源程序中的汇编指令和定义的数据,描述信息则主要是编译,连接程序对源代码中相关伪指令进行处理所得到的信息,我们在这个程序中,用伪指令end描述了程序的结束和程序的入口,在编译,连接后,由end start指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中。
在代码段中使用栈
完成下面的程序,利用栈,将程序中定义的数据逆向存放
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
?
codesg ends
end
程序的实现如下:
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0//用dw定义16个字符数据,在程序加载后,将取得16个字的内存空间,存放这16个数据,在后面的程序中将这段空间当作栈来使用
start: mov ax,cs
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
我们要将cs:10-cs:2F的内存空间当作栈来用,初始化栈为空,所以ss:sp要指向栈底,则设置ss:sp指向cs:10
在代码段中定义了16个字型数据,它们的数值都是0,这16个字符数据的值是多少,对程序来说没有意义,我们用dw定义了16个数据,即在程序中写入了16个字型数据,而程序在加载后,将用32个字节的内存来存放它们,这段内存空间是我们所需要的,程序将它作为栈空间。
将数据,代码,栈放入不同的段
将数据,栈和代码都放到了一个段里面,会有两个问题
- 把它们放到一个段里面使程序显得混乱
- 前面程序中处理的数据很少,用到的栈空间也小,加上没有多长的代码,放到一个段里面没有问题,但如果数据,栈和代码需要空间超过64KB,就不能放在一个段中(一个段的容量不能大于64KB,是我们学习中所用的8086模式的限制,并不是所有的处理器都这样)
所以应该考虑用多个段来存放数据,代码和栈
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
对段地址的引用
现在,程序中有多个段,如何访问段中的数据?当然要通过地址,而地址是分为两部分,即段地址和偏移地址,在程序中,段名就相当于一个标号,它代表了段地址,所以指令"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",将被编译器处理为一个表示段地址的数值