汇编语言:在代码段中使用栈
问题:完成下面的程序,利用栈,将程序中定义的数据逆序存放

程序运行时,定义的8个数据存放在CS:0~CS:F单元中,依次将这8个字单元中的数据入栈,然后再依次出栈,从而实现数据的逆序存放。
| 23 | CS:0 |
| 01 | CS:1 |
| 56 | CS:2 |
| 04 | CS:3 |
| 89 | CS:4 |
| 07 | CS:5 |
| BC | CS:6 |
| 0A | CS:7 |
| EF | CS:8 |
| 0D | CS:9 |
| ED | CS:A |
| 0F | CS:B |
| BA | CS:C |
| 0C | CS:D |
| 87 | CS:E |
| 09 | CS:F |
这是代码段中定义的8个字数据
既然需要栈,栈在内存中需要内存空间,所以,用dw定义“空”数据来开辟栈的内存空间,代码如下

内存空间开辟的空间如下
| 23 | CS:0 |
| 01 | CS:1 |
| 56 | CS:2 |
| 04 | CS:3 |
| 89 | CS:4 |
| 07 | CS:5 |
| BC | CS:6 |
| 0A | CS:7 |
| EF | CS:8 |
| 0D | CS:9 |
| ED | CS:A |
| 0F | CS:B |
| BA | CS:C |
| 0C | CS:D |
| 87 | CS:E |
| 09 | CS:F |
| 0 | CS:10 |
| 0 | CS:11 |
| 0 | CS:12 |
| 0 | CS:13 |
| 0 | CS:14 |
| 0 | CS:15 |
| 0 | CS:16 |
| 0 | CS:17 |
| 0 | CS:18 |
| 0 | CS:19 |
| 0 | CS:1A |
| 0 | CS:1B |
| 0 | CS:1C |
| 0 | CS:1D |
| 0 | CS:1E |
| 0 | CS:1F |
| 0 | CS:20 |
| 0 | CS:21 |
| 0 | CS:22 |
| 0 | CS:23 |
| 0 | CS:24 |
| 0 | CS:25 |
| 0 | CS:26 |
| 0 | CS:27 |
| 0 | CS:28 |
| 0 | CS:29 |
| 0 | CS:2A |
| 0 | CS:2B |
| 0 | CS:2C |
| 0 | CS:2D |
| 0 | CS:2E |
| 0 | CS:2F |
下面开始进行栈的位置定义,如下

mov ax,cs 将代码CS段地址复制并移动到寄存器AX中
mov ss,ax 栈段开始的位置就是CS:0
mov sp,30H 栈顶指针是CS:30
| 23 | CS:0 | |
| 01 | CS:1 | |
| 56 | CS:2 | |
| 04 | CS:3 | |
| 89 | CS:4 | |
| 07 | CS:5 | |
| BC | CS:6 | |
| 0A | CS:7 | |
| EF | CS:8 | |
| 0D | CS:9 | |
| ED | CS:A | |
| 0F | CS:B | |
| BA | CS:C | |
| 0C | CS:D | |
| 87 | CS:E | |
| 09 | CS:F | |
| 0 | CS:10 | |
| 0 | CS:11 | |
| 0 | CS:12 | |
| 0 | CS:13 | |
| 0 | CS:14 | |
| 0 | CS:15 | |
| 0 | CS:16 | |
| 0 | CS:17 | |
| 0 | CS:18 | |
| 0 | CS:19 | |
| 0 | CS:1A | |
| 0 | CS:1B | |
| 0 | CS:1C | |
| 0 | CS:1D | |
| 0 | CS:1E | |
| 0 | CS:1F | |
| 0 | CS:20 | |
| 0 | CS:21 | |
| 0 | CS:22 | |
| 0 | CS:23 | |
| 0 | CS:24 | |
| 0 | CS:25 | |
| 0 | CS:26 | |
| 0 | CS:27 | |
| 0 | CS:28 | |
| 0 | CS:29 | |
| 0 | CS:2A | |
| 0 | CS:2B | |
| 0 | CS:2C | |
| 0 | CS:2D | |
| 0 | CS:2E | |
| 0 | CS:2F | |
| CS:30 | 这是栈顶指针 |
再下一步,进行入栈的操作,代码如下

mov bx,0 代表取内存地址处的数据,也就是第一步取CS:0处的数据,取16位
mov cx,8 循环8次
push cs:[bx] 将cs:bx处的数据压入栈,而栈是向下生长的,所以第一次cs:bx处的数据为0123压入到CS:2E中,如下所示
| 23 | CS:0 | |
| 01 | CS:1 | |
| 56 | CS:2 | |
| 04 | CS:3 | |
| 89 | CS:4 | |
| 07 | CS:5 | |
| BC | CS:6 | |
| 0A | CS:7 | |
| EF | CS:8 | |
| 0D | CS:9 | |
| ED | CS:A | |
| 0F | CS:B | |
| BA | CS:C | |
| 0C | CS:D | |
| 87 | CS:E | |
| 09 | CS:F | |
| 0 | CS:10 | |
| 0 | CS:11 | |
| 0 | CS:12 | |
| 0 | CS:13 | |
| 0 | CS:14 | |
| 0 | CS:15 | |
| 0 | CS:16 | |
| 0 | CS:17 | |
| 0 | CS:18 | |
| 0 | CS:19 | |
| 0 | CS:1A | |
| 0 | CS:1B | |
| 0 | CS:1C | |
| 0 | CS:1D | |
| 0 | CS:1E | |
| 0 | CS:1F | |
| 0 | CS:20 | |
| 0 | CS:21 | |
| 0 | CS:22 | |
| 0 | CS:23 | |
| 0 | CS:24 | |
| 0 | CS:25 | |
| 0 | CS:26 | |
| 0 | CS:27 | |
| 0 | CS:28 | |
| 0 | CS:29 | |
| 0 | CS:2A | |
| 0 | CS:2B | |
| 0 | CS:2C | |
| 0 | CS:2D | |
| 23 | CS:2E | 第一次循环从栈顶压入 |
| 01 | CS:2F | |
| CS:30 | 栈顶指针 |
add bx,2 偏移地址+2,cs:bx就变为了cs:2
loop s 开始循环->push cs:[bx] ,此时地址为cs:2,取CS:2地址处的数据压入栈,如下
| 23 | CS:0 | |
| 01 | CS:1 | |
| 56 | CS:2 | |
| 04 | CS:3 | |
| 89 | CS:4 | |
| 07 | CS:5 | |
| BC | CS:6 | |
| 0A | CS:7 | |
| EF | CS:8 | |
| 0D | CS:9 | |
| ED | CS:A | |
| 0F | CS:B | |
| BA | CS:C | |
| 0C | CS:D | |
| 87 | CS:E | |
| 09 | CS:F | |
| 0 | CS:10 | |
| 0 | CS:11 | |
| 0 | CS:12 | |
| 0 | CS:13 | |
| 0 | CS:14 | |
| 0 | CS:15 | |
| 0 | CS:16 | |
| 0 | CS:17 | |
| 0 | CS:18 | |
| 0 | CS:19 | |
| 0 | CS:1A | |
| 0 | CS:1B | |
| 0 | CS:1C | |
| 0 | CS:1D | |
| 0 | CS:1E | |
| 0 | CS:1F | |
| 0 | CS:20 | |
| 0 | CS:21 | |
| 0 | CS:22 | |
| 0 | CS:23 | |
| 0 | CS:24 | |
| 0 | CS:25 | |
| 0 | CS:26 | |
| 0 | CS:27 | |
| 0 | CS:28 | |
| 0 | CS:29 | |
| 0 | CS:2A | |
| 0 | CS:2B | |
| 56 | CS:2C | 第二次循环压入栈 |
| 04 | CS:2D | |
| 23 | CS:2E | 第一次循环压入栈 |
| 01 | CS:2F | |
| CS:30 | 栈顶指针 |
如此循环8次后,如果如下
| 23 | CS:0 | |
| 01 | CS:1 | |
| 56 | CS:2 | |
| 04 | CS:3 | |
| 89 | CS:4 | |
| 07 | CS:5 | |
| BC | CS:6 | |
| 0A | CS:7 | |
| EF | CS:8 | |
| 0D | CS:9 | |
| ED | CS:A | |
| 0F | CS:B | |
| BA | CS:C | |
| 0C | CS:D | |
| 87 | CS:E | |
| 09 | CS:F | |
| 0 | CS:10 | |
| 0 | CS:11 | |
| 0 | CS:12 | |
| 0 | CS:13 | |
| 0 | CS:14 | |
| 0 | CS:15 | |
| 0 | CS:16 | |
| 0 | CS:17 | |
| 0 | CS:18 | |
| 0 | CS:19 | |
| 0 | CS:1A | |
| 0 | CS:1B | |
| 0 | CS:1C | |
| 0 | CS:1D | |
| 0 | CS:1E | |
| 0 | CS:1F | |
| 87 | CS:20 | 到此入栈完毕,栈指针停在此处 |
| 09 | CS:21 | |
| BA | CS:22 | |
| 0C | CS:23 | |
| ED | CS:24 | |
| 0F | CS:25 | |
| EF | CS:26 | |
| 0D | CS:27 | |
| BC | CS:28 | |
| 0A | CS:29 | |
| 89 | CS:2A | |
| 07 | CS:2B | |
| 56 | CS:2C | |
| 04 | CS:2D | |
| 23 | CS:2E | |
| 01 | CS:2F | |
| CS:30 |
可以看出来,指定好栈的范围后,入栈操作就是从栈顶开始向下生长。
pop指令格式:
pop + destination :destination是操作数,是指从当前栈指针指向的内存地址处读取数据,同时增加栈指针的值 ,将数据从栈中弹出后要存放的位置 ,而destination就是要存放的位置 。
push指令格式:
push + source :source是操作数,是指从栈的顶部向下移动,同时减小栈指针的值,将指定的数据压入栈中,而source就是指定的要压入栈中的数据来源位置。
了解了pop和push的命令后,我们知道 ,通过上述的push命令循环之后,目前的栈指针指向的是地址CS:20处,下面,使用出栈pop指令将栈指针处的数据弹出并存放在destination处。

mov bx,0 偏移地址重新定义为0,即cs:0
mov cx,8 循环8次
pop cs:[bx] 目前的栈指针是指向的CS:20处,弹出数据后,将数据存放在cs:[bx]处,也就是是CS:0处,栈指针增加,循环第一次的结果如下
| 87 | CS:0 | 第一次循环后,数据存放在此处 |
| 09 | CS:1 | |
| 56 | CS:2 | |
| 04 | CS:3 | |
| 89 | CS:4 | |
| 07 | CS:5 | |
| BC | CS:6 | |
| 0A | CS:7 | |
| EF | CS:8 | |
| 0D | CS:9 | |
| ED | CS:A | |
| 0F | CS:B | |
| BA | CS:C | |
| 0C | CS:D | |
| 87 | CS:E | |
| 09 | CS:F | |
| 0 | CS:10 | |
| 0 | CS:11 | |
| 0 | CS:12 | |
| 0 | CS:13 | |
| 0 | CS:14 | |
| 0 | CS:15 | |
| 0 | CS:16 | |
| 0 | CS:17 | |
| 0 | CS:18 | |
| 0 | CS:19 | |
| 0 | CS:1A | |
| 0 | CS:1B | |
| 0 | CS:1C | |
| 0 | CS:1D | |
| 0 | CS:1E | |
| 0 | CS:1F | |
| 87 | CS:20 | |
| 09 | CS:21 | |
| BA | CS:22 | 栈指针自增后,指针指向此处 |
| 0C | CS:23 | |
| ED | CS:24 | |
| 0F | CS:25 | |
| EF | CS:26 | |
| 0D | CS:27 | |
| BC | CS:28 | |
| 0A | CS:29 | |
| 89 | CS:2A | |
| 07 | CS:2B | |
| 56 | CS:2C | |
| 04 | CS:2D | |
| 23 | CS:2E | |
| 01 | CS:2F | |
| CS:30 |
如此循环8次后,结果如下
| 87 | CS:0 | |
| 09 | CS:1 | |
| BA | CS:2 | |
| 0C | CS:3 | |
| ED | CS:4 | |
| 0F | CS:5 | |
| EF | CS:6 | |
| 0D | CS:7 | |
| BC | CS:8 | |
| 0A | CS:9 | |
| 89 | CS:A | |
| 07 | CS:B | |
| 56 | CS:C | |
| 04 | CS:D | |
| 23 | CS:E | |
| 01 | CS:F | |
| 0 | CS:10 | |
| 0 | CS:11 | |
| 0 | CS:12 | |
| 0 | CS:13 | |
| 0 | CS:14 | |
| 0 | CS:15 | |
| 0 | CS:16 | |
| 0 | CS:17 | |
| 0 | CS:18 | |
| 0 | CS:19 | |
| 0 | CS:1A | |
| 0 | CS:1B | |
| 0 | CS:1C | |
| 0 | CS:1D | |
| 0 | CS:1E | |
| 0 | CS:1F | |
| 87 | CS:20 | |
| 09 | CS:21 | |
| BA | CS:22 | |
| 0C | CS:23 | |
| ED | CS:24 | |
| 0F | CS:25 | |
| EF | CS:26 | |
| 0D | CS:27 | |
| BC | CS:28 | |
| 0A | CS:29 | |
| 89 | CS:2A | |
| 07 | CS:2B | |
| 56 | CS:2C | |
| 04 | CS:2D | |
| 23 | CS:2E | |
| 01 | CS:2F | |
| CS:30 |
DOSBOX-X的实操
将上述代码存储 为p6-3.asm,并在DOSBOX-X中进行编译

IP(Instruction Pointer)指令指针寄存器:
可以看到IP=0030,说明指令执行是从上面的CS:30地址处开始执行的。

assume cs:code
code segment
; 第一块:8个字型数据(dw=define word,每个占2字节,共16字节)
dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
; 第二块:16个0(作为栈空间,共32字节)
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
start: ; 程序入口
; 栈初始化:栈段=代码段,栈顶指针sp=30H(十进制48)
mov ax,cs
mov ss,ax
mov sp,30H
; 第一步:将前8个数据依次压入栈
mov bx,0 ; 偏移地址初始化为0(指向第一个数据0123H)
mov cx,8 ; 循环次数=8(对应8个数据)
s: push cs:[bx] ; 将cs:bx指向的数据压栈
add bx,2 ; bx+2(指向下一个字型数据)
loop s ; cx--,cx≠0则跳回s
; 第二步:将栈中数据依次弹出,写回原数据区
mov bx,0 ; 偏移地址重置为0
mov cx,8 ; 循环次数重置为8
s0: pop cs:[bx] ; 栈顶数据弹出到cs:bx指向的内存
add bx,2 ; bx+2
loop s0 ; cx--,cx≠0则跳回s0
; 程序退出
mov ax,4c00h
int 21h
code ends
end start
以上是对整体代码的总结解读。
可以看出来,数据、栈和代码都是在一个段中的,程序显得混乱,尤其是程序更加庞大的时候,编程和阅读时都要注意何处是数据,何处是栈,何处是代码,数据量太大了,非常混乱。上面这种程序只适用于处理数据很少的情况,用到的栈也很小,代码没有多长。
现实中的高级语言中,都是将数据、代码、栈存放在不同的段中。

将p6-4.asm 放入DOSBOX-X中进行编译并运行

IP=0000,可以看出来,代码执行是从0E29:0000处开始执行的。

浙公网安备 33010602011771号