栈(Stack)
栈是一种**后进先出(LIFO,Last In First Out)**的数据结构,在汇编中由 CPU 直接支持,通过 SS:SP 两个寄存器操作。
前置知识
栈的基本概念
8086 的栈有两个关键特性:
- 栈从高地址向低地址增长(和代码、数据的增长方向相反)
- 栈操作以字(2字节)为单位,push 和 pop 都操作 16 位数据
两个寄存器控制栈:
- SS(Stack Segment):栈段寄存器,存放栈所在段的段地址
- SP(Stack Pointer):栈顶指针,存放栈顶的偏移地址
SS:SP 永远指向栈顶元素(即最后压入栈的数据)。
push 指令(入栈)
格式:push 寄存器/内存单元/段寄存器
执行 push ax 时,CPU 做两件事:
SP = SP - 2 ; 栈顶向上(低地址方向)移动2字节
将 AX 的值送入 SS:SP 指向的字单元 ; 数据写入新的栈顶位置
mov ax, 0123H
push ax ; SP 减2,将 0123H 压入栈
push ds:[0] ; 将内存中的字压入栈
push cs ; 段寄存器也可以压栈
pop 指令(出栈)
格式:pop 寄存器/内存单元/段寄存器(注意:不能 pop CS!)
执行 pop ax 时,CPU 做两件事:
将 SS:SP 指向的字单元数据送入 AX ; 从栈顶读取数据
SP = SP + 2 ; 栈顶向下(高地址方向)回缩2字节
pop ax ; 栈顶数据弹出到 AX
pop ds:[0] ; 栈顶数据弹出到内存
pop ds ; 弹出到段寄存器(CS 除外!)
pop CS 为什么不行?
和不能
mov cs, ax一样,pop CS 会直接改变 CS 的值,导致 CPU 跳到错误位置执行。修改 CS 只能通过转移指令(jmp/call/ret/int)。
栈的可视化
假设 SS=1000H,SP=0010H,依次执行 push ax、push bx、pop cx:
初始状态(SP=0010H,栈空):
地址 内容
1000:00 ..
1000:02 ..
1000:04 ..
1000:06 ..
1000:08 ..
1000:0A ..
1000:0C ..
1000:0E ..
1000:10 .. ← SP 指向这里(栈底/空栈位置)
push ax(假设 AX=1122H,SP=000EH):
1000:0E 22H ← SP
1000:0F 11H
push bx(假设 BX=3344H,SP=000CH):
1000:0C 44H ← SP
1000:0D 33H
1000:0E 22H
1000:0F 11H
pop cx(CX=3344H,SP=000EH):
1000:0E 22H ← SP
1000:0F 11H
(1000:0C 和 1000:0D 的数据并没有被"删除",只是 SP 不再指向它们了,下次 push 会覆盖)
栈顶超界问题
8086 不会自动检查栈顶是否越界!如果 push 太多次(SP 减到0以下)或 pop 太多次(SP 超过栈底),会覆盖栈外的数据或代码,导致不可预测的错误。程序员必须自己保证栈的大小足够。
栈的用途
1. 临时保存寄存器的值
当你需要使用一个寄存器,但不想破坏它原来的值时:
push ax ; 保存 AX
push bx ; 保存 BX
mov ax, 1000H ; 使用 AX 和 BX 做事情
mov bx, 2000H
; ... 一些操作
pop bx ; 恢复 BX(注意顺序:先进后出!)
pop ax ; 恢复 AX
push 和 pop 的顺序
先 push 的要后 pop,顺序必须相反。就像往箱子里放衣服,先放的在最下面,最后才能拿出来。
2. 数据传递(内存到内存的间接方式)
x86 不支持内存到内存的直接 mov,但可以用栈中转:
push [0] ; 把 ds:0 的字压栈
pop [2] ; 弹出到 ds:2,相当于把 [0] 的数据复制到 [2]
3. 函数调用(核心用途)
call 和 ret 指令就是用栈来保存返回地址的,这是栈最重要的用途。
4. 子程序间传递参数、保存局部变量
高级语言(C语言等)的函数调用栈帧就是用这个机制实现的。
在程序中设置栈
完整的栈设置方法(详见 05-多段程序设计):
stack segment
dw 16 dup(0) ; 分配 16 个字 = 32 字节的栈空间
stack ends
code segment
start:
mov ax, stack
mov ss, ax
mov sp, 32 ; SP = 栈空间大小(字节数)。初始时栈空,SP 指向栈空间最顶端+1的位置
; ...
code ends
为什么 SP 初始值设为栈空间大小?因为栈从高地址往低地址增长,空栈时 SP 指向栈空间末尾之后的位置,第一次 push 时 SP 减 2 恰好指向栈空间最高的字单元。
push/pop 支持的操作数
; 寄存器
push ax
pop bx
; 段寄存器
push ds
pop es
; 内存单元
push word ptr [bx]
pop word ptr [si]
; ❌ 不支持的操作
push 1234H ; 8086 不支持 push 立即数(80186以后才支持)
pop cs ; 不能 pop CS
push al ; ❌ push/pop 只操作字(16位),不能操作字节
相关
- 01-寄存器 — SS/SP 寄存器介绍
- 05-多段程序设计 — 如何在程序中设置栈段
- 08-过程调用与算术指令 — call/ret 利用栈实现子程序调用