截至目前为止,我们所编写的汇编程序中有且只有一个段,那就是代码段。代码段当然是必不可少的,要不然我们的代码放在哪里呢!
在上篇博文中,我们学习了如何在代码段中申请空间,将申请过来的空间用来当作数据段和栈段来使用。这样的话,也就是说我们源程序中,数据段和栈段都在代码段内,它们共用同一个段地址。
这样就导致我们在编程时,要注意何处是数据,何处是栈,何处是代码。所以出现了两个问题:
1、把它们放在一个段中使程序显得混乱。程序结构不鲜明,不符合开发规范
2、我们上篇博文中的程序中处理的数据很少,且栈空间也小,代码也没有多长,放到一个段里面也没多大问题。如果数据、栈、代码所需的空间超过64KB,那么就不能放在一个段中了。因为一个段最大为64KB,即偏移范围:0~FFFFH。(总字节大小:65536B = 64KB)
所以,我们要把各个段放在它们应该待的段里。那么就需要定义数据段,来专门存放待操作的数据;需要定义栈段, 来专门进行栈访问。
这也就是本篇博文中我们将要学习到的地方。
四大段寄存器中,谁和数据走得最近呢?答案当然是DS段寄存器!那么定义数据段,自然和DS少不了关系。
还记得我们是如何在源程序中定义代码段的吗?使用 assume 伪指令。
assume cs:code
如此我们便定义了代码段CS,并将它和标号 code 绑定在一起。如果我们定义数据段呢?很简单,还是使用 assume 伪指令:
assume cs:code,ds:data
同一个语句,只需要在后面加上语句:ds:data,并使用","隔开,这样我们就定义了数据段DS,并将它和标号:data 绑定在一起。
上面我们已经使用 assume 定义了数据段DS,那么我们该如何在源程序中对它进行开发和使用呢?其实很简单,源程序中编写段的格式基本都是一致的,我们熟悉代码段的开发编写,自然就熟悉数据段。
assume cs:code,ds:data
data segment
dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
data ends
如上,和代码段一样,data segment 表示数据段的开始,data ends 表示数据段结束。在他们之间则是数据段的内容:我们使用dw申请了8个字型数据大小的空间并做了初始化,那么也就是说我们定义的数据段大小就为8个字数据16个字节大小。
现在我们定义好了数据段,也申请好了数据段的空间,那么该如何在程序中使用呐?接下来的使用就需要在代码段中操作。
mov ax,data
mov ds,ax
如上这两条指令,就是设置DS段的段地址。标号 data 就是我们在源程序开头使用 assume 将数据段DS与之绑定的标号,而标号 data 经过编译器编译后,则会变成一个地址,该地址是我们定义的数据段的段地址。那么指令语句:mov ax,data,含义就是将源程序中定义的数据段的段地址送入AX寄存器中。下面的指令语句:mov ds,ax,大家就很熟悉了,就是设置数据段的段地址,以便我们可以在程序中访问数据段。
已知SS段寄存器是和栈相关,所以定义栈段自然是和SS密不可分。和定义数据段一样,相同的格式。
assume cs:code,ss:stack
如上,还是使用assume伪指令来定义,ss:stack 意思为:定义SS段,使用标号 stack来表示代替。
基本格式还是和数据段、代码段的格式保持一致:
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
stack segment 表示栈段的开始,stack ends 表示栈段的结束。在他们之间是栈段的内容:我们使用 dw 申请了16个字型数据大小的内存空间,并用0对空间进行初始化填充。
在代码段中如何使用定义好的栈段呢?基本格式也是和使用数据一样:
mov ax,stack
mov ss,ax
mov sp,20H
如上代码指令,就是在代码段中设置栈段。和设置数据段不同的是,我们不止要设置SS的值,还要设置SP的值,因为栈段的访问是由SS:SP决定的,切记不能将SP给拉下。前面已经说过,标号经过编译后就变成了其对应的段的段地址,所以这里标号 stack 就代表是栈段的段地址。mov ax,stack、mov ss,ax,这两条代码指令就表示将栈段设置为我们定义的栈,以便我们在程序中使用。
这里我们给SP的值是20H,为什么是20H呢?我们还记得上篇博文中,在代码段中定义使用栈,其中SP我们设置为30H,这里有什么不同之处么?
code 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
start:
如上就是我们在代码段中定义的栈段。我们发现栈空间长度长度为20H,在栈空间之前还有一个长度为10H的数据段,所以对于栈段来说,栈底就是2FH,那么SP自然就要设置为30H。倘如,我们修改一下,将栈段的定义放在前面,数据段的定义放在后面,如下:
code segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
start:
则,栈底就变成了1FH,SP就需要设置为20H了。
那么现在我们将栈段拉了出去,自己独立成段,自然栈段开始的地方就是 stack:0H,栈底就是 stack:1FH,所以SP就要设置为20H。
我们已经学习了如何在源程序中定义和使用数据段、栈段,那么现在我们就开始把他们综合在一起进行学习吧。
我们还是以上篇博文中的编程例子学习,将其改为使用多个段,源程序如下:
assume cs:code,ds:data,ss:stack ; 定义代码段,标号为code;定义数据段,标号为data;定义栈段,标号为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,data
mov ds,ax ; 设置ds为定义的数据段
mov ax,stack
mov ss,ax ; 设置ss为定义的栈段
mov sp,20H ; 设置sp指向栈底
mov bx,0 ; 数据段偏移从0开始
mov cx,8 ; 循环8次
s:push ds:[bx] ; 将数据段中指定偏移下的字字型数据入栈
add bx,2 ; 字型数据长度2个字节,所以偏移加2
loop s ; 判断循环
; 第二次循环开始
mov bx,0 ; 数据段偏移从0开始
mov cx,8 ; 循环8次
s0:pop ds:[bx] ; 将栈中的数据出栈放到数据段中指定偏移下
add bx,2 ; 偏移加2
loop s0 ; 判断循环
mov ax,4c00H
int 21H ; 程序返回
code ends ; 代码段结束
end start ; 源程序结束,并指定程序开始位置
博主对上述源程序代码每行都做了相应的注释,相信小伙伴们都能看的很明白了。那么下面就让我们编译连接,在Debug中跟踪查看一下吧:
我们会发现,在明明已经在源程序中定义声明栈段的情况下,连接器还是给出一个警告说没有栈段。这里其实是一个小Bug,我们不必上心理会。
使用Debug 将其加载进内存后,我们R命令查看,当前CS:IP入口在076D:0H,代码指令为:mov ax,076AH。我们知道,源程序代码段中第一行指令为:mov ax,data,所以我们可以得知标号 data 经过编译后变成了段地址:076AH,这是数据段的段地址。
因为源程序中申请内存空间是一个连续的内存空间,所以我们可以查看下从数据段开始,内存中数据的详情:
我们可以看到:内存空间 076A:0~076A:F 这16个字节是我们定义的数据段,里面的数据放着的是待操作的8个子型数据;内存空间 076A:10H~076A:2FH 这32个字节是我们定义的栈段,初始化数据全部为0;后面从地址 076A:30H 开始,则是我们的代码段,存放着代码数据。
我们使用U命令查看下代码:
我们可以看到,在设置SS的指令部分,代码指令:mov AX,076BH,我们知道源程序中为:mov ax,stack,所以标号 stack经过编译后变成了段地址:076BH,这个栈段的段地址。
到这里我们会发现一个现象,那就是:每个段的段地址都是不相同的,但是起始偏移位置却是相同的。比如:数据段的起始地址为:076A:0H;栈段的起始地址为:076B:0H,代码段的起始地址为:076DH:0H。为什么是这个样子呢?其实我么们之前也有讲述过。还记得述程序被加载执行的过程吗?之所以会是这个样子是为了DOS系统更好地区分程序中不同的段,这种通过段地址的区分更加符合人们的认知和理解,当然对于CPU来说实际上并不care。
所以我们只需要记住,我们在源程序中定义了多个段,每个段的起始偏移都是从0H开始的。
题目1中,我们直接看第3小题:
这个我们上面才说过,多个段的存在,段地址不同,起始偏移相同。从给出的源程序中我们可以看出,首先声明定义了数据段 data 共16个字节,其次声明定义了栈段 stack 共16个字节,最后声明定义了代码段 code。已知代码段的段地址为X,data段和code段之间相差32个字节,所以 data 段的段地址为:X-2。stack段和code段之间相差16个字节,所以stack段为:X-1。
下面我们说下题目4:
针对这个问题,我们直接拿上面 综合使用 中的源程序当例子: 我们将end start改为 end:
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,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,20H
mov bx,0
mov cx,8
s:push ds:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0:pop ds:[bx]
add bx,2
loop s0
mov ax,4c00H
int 21H
code ends
end
改完后,我们编译连接运行:
哦豁,CS:IP的指向居然是我们定义的数据段的起始地址!这显然是错误的。
我们再修改一下,将代码段的定义放在最前面:
assume cs:code,ds:data,ss:stack
code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,20H
mov bx,0
mov cx,8
s:push ds:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0:pop ds:[bx]
add bx,2
loop s0
mov ax,4c00H
int 21H
code ends
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
end
编译连接运行:
此时,CS:IP指向为:076a:0H,这也是代码段的起始位置,指令开始执行的地方。通过这两次对比我们可以得出出结论:
在源程序中没有指明程序入口的情况下,将会默认把源程序中最先定义的那个段的起始地址当作程序入口地址。CS设置为该段的段地址,IP设置为该段的起始偏移地址0。
在源程序中指明程序入口的情况下,将会把入口标号所在处当作程序的入口地址。CS设置为标号所在段的段地址,IP设置为标号所在的偏移地址。
我们可以看到,此时数据段的段地址为:076DH,按照惯例我们查看下此时内存中数据情况:
我们可以看到,内存空间 076A:0~076A:2FH 为代码段,占用了30H个字节大小,所以数据段的段地址为:076A+3 = 076DH,那么内存空间 076D:0~076D:F 为数据段,占用10H个字节大小。下面内存空间 076E:0~076E:1F 就是我们定义的栈段,共20H个字节,使用0进行填充。
回到我们的问题,去掉指明程序入口后,谁可以正确执行呢?答案当然是源程序中 code 段最先被声明定义的可以正确执行!
下面我们来看另外一个问题:
我们观察还会发现一个东西,请注意看内存地址 076A:2A 下的数据,这是指令:int 21H。我们知道 int 21H 是程序返回指令,标志着程序到此结束。到这里也是代码段结束,那么按照连续的内存地址空间,数据段的起始地址应该为:076A:2C,怎么滴就是 076D:0?
这是因为多个段,段地址不同,起始偏移为0。如果我们将 076A:2C当作数据段的起始地址,首先它的起始偏移要为0,那么段地址就为:076A:2C ➗ 16,这时候你就会发现段地址就没法计算得出了,变成了一个带有小数点的数字。小数点的数字显然是不能够当段地址的,因为CPU就没法计算目的地址了。
那么必须要整除16才能当作段的段地址。所以,申请的内存空间大小默认都是16的整数倍,不足16的整数倍,会自动补上以符合要求。也就是说就算你在源程序中申请的是2个字节,编译连接后,会自动变成16个字节空间。
上图中就可以看到,代码段的实际占用内存空间大小为:2CH,2CH不是16的整数倍,所以就补上了4个字节变成了 2C+4 = 30H。我们能看到补上的4个字节:076A:2C~076A:2FH,其内容都是0。
我们明白了这个,转头看题目2,源程序:
问,已知code段的段地址为X,求data段的段地址和stack段的段地址?
我们可以看到源程序中,data段和stack段都是只申请了两个字型数据的空间。按照我们上述的知识点,由于段中申请的空间大小不能整除16,所以会自动补充扩充到16的倍数。也就是说虽然我们在源程序中只申请了4个字节大小,但是实际上却是16个字节。
我们使用Debug加载题目中的源程序,D命令查看内存空间数据:
所以题目答案为:data段的段地址为:X- 2,stack段的段地址为:X-1。
本篇博文中,我们主要学习了如何在源程序中声明定义多个段。此外探讨并解答了课后习题中的知识点,巩固了学习认知。
下篇博文中,我们将学习如何进行灵活寻址,这部分是汇编开发中比较重要的部分,我们将着重讲解。
感谢围观,转发分享请标明出处,谢谢!