文章目录
前言
1. 引言
前面的程序中,只有一个代码段。现在有一个问题是,如果程序需要用其他空间来存放数据,使用哪里呢?前面的内容中,我们讲到要使用一段安全的空间。可哪里安全呢?
在操作系统的环境中,合法地通过操作系统取得的空间都是安全的,因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间相冲突。在操作系统允许的情况下,程序可以取得任意容量的空间。
加载程序的时候为程序分配空间,我们在前面已经有所体验,比如我们的程序在加载的时候,取得了代码段中的代码的存储空间。
我们若要一个程序在被加载的时候取得所需的空间,则必须要在源程序中做出说明。。
上面是从内存空间获取的角度上,谈定义段的问题。我们再从程序规划的角度来谈一下定义段的问题。大多数有用的程序,都要处理数据,使用栈空间,当然也都必须有指令,为了程序设计上的清晰和方便,我们一般也都定义不同的段来存放它们。
对于使用多个段的问题,我们先简单说到这里,后面我们将以这样的顺序来深入地讨论多个段的问题:
(1)在一个段中存放数据、代码、栈,我们先来体会一下不使用多个段时的情况。
(2)将数据、代码、栈放入不同的段中。
本文先来看第一点。
2. 在代码段中使用数据
2.1 问题引入
考虑这样一个问题,编程计算以下8个数据的和,结果存在ax寄存器中:
0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h
在前面的文章中,我们都是累加某些内存单元中的数据,并不关心数据本身。可现在要累加的就是已经给定了数值的数据。我们可以将它们一个一个地加到ax寄存器中,但是,我们希望可以用循环的方法来进行累加,所以在累加前,要将这些数据存储在一组地址连续的内存单元中。
如何将这些数据存储在一组地址连续的内存单元中呢?,可是这样又有一个问题,到哪里去找这段内存空间呢?
从规范的角度来讲,我们是不能自己随便决定哪段空间可以使用的,应该让系统来为我们分配。我们可以在程序中,定义我们希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中。
2.2 代码实现
具体的做法看下面的程序。
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 2lh
code ends
end
2.3 代码分析
2.3.1 提出问题
(1)程序中的指令就要对这8个数据进行累加,可这8个数据在哪里呢?
由于它们在代码段中,程序在运行的时候CS中存放代码段的段地址,所以可以。
(2)它们的偏移地址是多少呢?
。程序运行时,它们的地址就是CS:0、CS:2、CS:4、CS:6、CS:8、CS:A、CS:C、CS:E。
(3)
程序中,用bx存放加2递增的偏移地址,用循环来进行累加。在循环开始前,设置(bx)=0,cs:bx 指向第一个数据所在的字单元。每次循环中(bx)=(bx)+2,cs:bx 指向下一个数据所在的字单元。
2.3.2 编译、链接、加载、查看程序
将上面的程序编译、连接为可执行文件p61.exe,先不要运行,用Debug 加载查看下,情况如下图所示。
上图中,通过“DS=0B2D”,可知道程序从0B3D:0000开始存放。用u命令从0B3D:0000查看程序,却看到了一些让人读不懂的指令。
但是,为什么没有看到程序中的指令呢?
实际上用u命令从0B3D:0000查看到的也是程序中的内容,只不过不是源程序中的汇编指令所对应的机器码,而是源程序中,在汇编指令前面,用dw定义的数据。实际上,在我们的程序中,有一个代码段,在代码段中,前面的16个字节是用“dw”定义的数据,从第16个字节开始才是汇编指令所对应的机器码。
2.3.3 查看内存内容
可以用d命令更清楚地查看一下程序中前16个字节的内容,如下图所示。
2.3.4 正确的执行程序
可以从0B3D:0010查看程序中要执行的机器指令,如下图所示。
从上面两个图中,我们可以看到程序加载到内存中后,。
怎样执行程序中的指令呢?
用Debug加载后,可以将IP设置为10h,从而使CS:IP指向程序中的第一条指令。然后再用t命令、p命令,或者是g命令执行。
2.4 面临的问题
可是这样一来,我们就必须用Debug来执行程序。之前的程序编译、连接成可执行文件后,在系统中直接运行可能会出现问题,。
2.5 改进代码
如何让这个程序在编译、连接后可以在系统中直接运行呢?
我们可以在源程序中所在,具体做法如下。
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
2.5.1 程序的执行过程
注意在上面的程序中加入的新内容,在程序的第一条指令的前面加上了一个标号start,而这个标号在伪指令end的后面出现。这里,我们要再次探讨end的作用。。在上面的程序中我们用end指令指明了程序的入口在标号 start 处,也就是说,“mov bx,0
”是程序的第一条指令。
2.5.2 CPU如何知道第一条执行什么指令?
现在的问题是,根据什么设置CPU的CS:IP指向程序的第一条要执行的指令?也就是说,如何知道哪一条指令是程序的第一条要执行的指令?
这一点,是由可执行文件中的描述信息指明的。我们知道,;。
我们在刚才的程序中,用伪指令end描述了程序的结束和程序的入口。在编译、连接后,由“end start
”指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中。
在刚才程序生成的可执行文件中,这个入口地址的偏移地址部分为:10H。当程序被加载入内存之后,加载者从程序的可执行文件的描述信息中读到程序的入口地址,设置CS:IP。这样CPU就从我们希望的地址处开始执行。
归根结底,我们若要CPU从何处开始执行程序,只要在源程序中用“end 标号
”指明就可以了。
2.6 程序的框架
有了这种方法,就可以这样来安排程序的框架:
assume cs:code
code segment
;
;
数据
;
;
start:
;
;
代码
;
;
code ends
end start
3. 在代码段中使用栈
3.1 问题引入
完成下面的程序,利用栈,将程序中定义的数据逆序存放。
assume cs:codesg
codesq segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
?
codesg ends
end
程序的思路大致如下。
程序运行时,定义的数据存放在cs:0~cs:F单元中,共8个字单元。
问题是,我们首先要有一段可当作栈的内存空间。
如前所述,这段空间应该由系统来分配。。
3.2 代码实现
程序实现如下。
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 ;指明程序的入口在 start 处
3.3 重要说明
(1)
注意上面程序中的指令:
mov ax,cs
mov ss,aX
mov sp,30h
我们要将cs:10~cs:2F的内存空间当作栈来用,,则设置 ss:sp指向 cs:30。如果对这点还有疑惑,建议回头认真复习一下之前栈的相关内容。
(2)
在代码段中定义了16个字型数据,它们的数值都是0。这16个字型数据的值是多少,对程序来说没有意义。
我们用dw定义16个数据,即在程序中写入了16个字型数据,而程序在加载后,将用32个字节的内存空间来存放它们。这段内存空间是我们所需要的,程序将它用作栈空间。可见,我们定义这些数据的最终目的是,通过它们取得一定容量的内存空间。
所以我们在描述dw的作用时,可以说用它定义数据,也可以说用它开辟内存空间。比如对于:
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
,这段空间中每个字单元中的数据依次是:0123h、0456h、0789h、0abch、0deth、0fedh、0cbah、0987h。因为它们最终的效果是一样的。
结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。
也可以点点关注,避免以后找不到我哦!
Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!