我正在准备微处理器考试。如果程序计数器的用途是保存下一条指令的地址,那么堆栈指针有什么用?
9 回答
堆栈是一种 LIFO(后进先出 - 您推入堆栈的最后一个条目是您弹出时返回的第一个条目)数据结构,通常用于保存堆栈帧(堆栈的位属于当前函数)。
这包括但不限于:
- 退货地址。
- 返回值的地方。
- 传递的参数。
- 局部变量。
您将项目推入堆栈并将它们弹出。在微处理器中,堆栈既可以用于用户数据(例如局部变量和传递的参数),也可以用于CPU 数据(例如调用子程序时的返回地址)。
堆栈的实际实现取决于微处理器架构。它可以在内存中向上或向下增长,并且可以在 push/pop 操作之前或之后移动。
通常影响堆栈的操作是:
- 子程序调用和返回。
- 中断调用和返回。
- 代码显式推送和弹出条目。
- 直接操作 SP 寄存器。
在我的(虚构的)汇编语言中考虑以下程序:
Addr Opcodes Instructions ; Comments
---- -------- -------------- ----------
; 1: pc<-0000, sp<-8000
0000 01 00 07 load r0,7 ; 2: pc<-0003, r0<-7
0003 02 00 push r0 ; 3: pc<-0005, sp<-7ffe, (sp:7ffe)<-0007
0005 03 00 00 call 000b ; 4: pc<-000b, sp<-7ffc, (sp:7ffc)<-0008
0008 04 00 pop r0 ; 7: pc<-000a, r0<-(sp:7ffe[0007]), sp<-8000
000a 05 halt ; 8: pc<-000a
000b 06 01 02 load r1,[sp+2] ; 5: pc<-000e, r1<-(sp+2:7ffe[0007])
000e 07 ret ; 6: pc<-(sp:7ffc[0008]), sp<-7ffe
现在让我们跟踪执行,描述上面评论中显示的步骤:
- 这是程序计数器为零且堆栈指针为 8000(所有这些数字都是十六进制)的起始条件。
- 这只是使用立即值 7 加载寄存器 r0 并移至下一步(我假设您了解默认行为将移至下一步,除非另有说明)。
- 这会将 r0 推入堆栈,方法是将堆栈指针减 2,然后将寄存器的值存储到该位置。
- 这调用了一个子程序。将程序计数器以与上一步中的 r0 类似的方式推入堆栈,然后将程序计数器设置为其新值。这与用户级推送没有什么不同,只是它更多地是作为系统级的事情完成的。
- 这将从堆栈指针计算的内存位置加载 r1 - 它显示了一种将参数传递给函数的方法。
- return 语句从堆栈指针指向的位置提取值并将其加载到程序计数器中,同时向上调整堆栈指针。这就像系统级弹出(请参阅下一步)。
- 从堆栈中弹出 r0 涉及从堆栈指针指向的位置提取值,然后向上调整该堆栈指针。
- 停止指令只是将程序计数器留在原处,一种无限循环。
希望从那个描述中,它会变得清晰。底线是:堆栈对于以 LIFO 方式存储状态很有用,这对于大多数微处理器执行子程序调用的方式通常是理想的。
除非您当然是SPARC,否则在这种情况下,您可以为堆栈使用循环缓冲区:-)
更新:只是为了阐明在上述示例中推送和弹出值时所采取的步骤(无论是显式还是通过调用/返回),请参见以下示例:
LOAD R0,7
PUSH R0
Adjust sp Store val
sp-> +--------+ +--------+ +--------+
| xxxx | sp->| xxxx | sp->| 0007 |
| | | | | |
| | | | | |
| | | | | |
+--------+ +--------+ +--------+
POP R0
Get value Adjust sp
+--------+ +--------+ sp->+--------+
sp-> | 0007 | sp->| 0007 | | 0007 |
| | | | | |
| | | | | |
| | | | | |
+--------+ +--------+ +--------+
堆栈指针存储最近被压入堆栈的条目的地址。
要将值压入堆栈,堆栈指针会递增以指向下一个物理内存地址,并将新值复制到内存中的该地址。
要从堆栈中弹出一个值,从堆栈指针的地址复制该值,堆栈指针递减,指向堆栈中的下一个可用项。
硬件堆栈最典型的用途是存储子程序调用的返回地址。当子程序完成执行时,返回地址从栈顶弹出并放入程序计数器寄存器中,使处理器在子程序调用之后的下一条指令处恢复执行。
http://en.wikipedia.org/wiki/Stack_%28data_structure%29#Hardware_stacks
你有更多的准备[为考试]做;-)
堆栈指针是一个寄存器,它保存堆栈上下一个可用点的地址。
堆栈是内存中保留用于存储堆栈的区域,即 LIFO(后进先出)类型的容器,我们在其中存储局部变量和返回地址,从而可以简单地管理函数调用的嵌套一个典型的程序。
有关堆栈管理的基本说明,请参阅此Wikipedia 文章。
对于 8085:堆栈指针是微处理器中一个特殊用途的 16 位寄存器,它保存堆栈顶部的地址。
计算机中的堆栈指针寄存器可供以低于中断处理程序的特权级别执行的程序进行通用使用。此类程序中的一组指令(不包括堆栈操作)将堆栈指针以外的数据(例如操作数等)存储在堆栈指针寄存器中。当在中断上将执行切换到中断处理程序时,当前执行程序的返回地址数据被压入中断处理程序特权级别的堆栈。因此,将其他数据存储在堆栈指针寄存器中不会导致堆栈损坏。此外,这些指令可以将数据存储在当前堆栈指针之外的堆栈段的临时部分中。
阅读这篇文章了解更多信息。
堆栈是用于保存临时数据的内存区域。CALL 指令使用堆栈来保存过程的返回地址。 return RET 指令从堆栈中获取该值并返回到该偏移量。当 INT 指令调用中断时也会发生同样的情况。它在堆栈中存储标志寄存器、代码段和偏移量。IRET 指令用于从中断调用返回。
堆栈是后进先出 (LIFO) 存储器。数据通过 PUSH 指令放入堆栈,并通过 POP 指令删除。堆栈存储器由两个寄存器维护:堆栈指针 (SP) 和堆栈段 (SS) 寄存器。当一个数据字被压入堆栈时,高 8 位字节被放置在位置 SP-1 中,低 8 位字节被放置在位置 SP-2 中。然后 SP 减 2。SP 添加到 (SS x 10H) 寄存器,以形成物理堆栈内存地址。当数据从堆栈中弹出时,会发生相反的顺序。当从堆栈中弹出一个数据字时,在位置 SP-1 中获得高位 8 位字节,在位置 SP-2 中获得低位 8 位字节。然后 SP 增加 2。
堆栈指针保存堆栈顶部的地址。堆栈允许函数将存储在堆栈中的参数相互传递,并创建作用域变量。此上下文中的范围意味着当堆栈帧消失和/或函数返回时,变量从堆栈中弹出。如果没有堆栈,您将需要为所有内容使用显式内存地址。这将使为架构设计高级编程语言变得不可能(或至少非常困难)。此外,每种 CPU 模式通常都有自己的存储堆栈指针。因此,当发生异常(例如中断)时,异常处理程序例程可以使用自己的堆栈,而不会破坏用户进程。
在某些 CPU 上,有一组专用的堆栈寄存器。执行调用指令时,一个寄存器加载程序计数器,同时第二个寄存器加载第一个寄存器的内容,第三个寄存器加载第二个寄存器,第四个寄存器加载第三个,等等. 当执行返回指令时,程序计数器被第一个堆栈寄存器的内容锁存,同时该寄存器从第二个寄存器被锁存;第二个寄存器是从第三个寄存器加载的,等等。请注意,这样的硬件堆栈往往相当小(例如,许多较小的 PIC 系列微控制器具有两级堆栈)。
虽然硬件堆栈确实具有一些优势(例如,推送和弹出不会为调用/返回增加任何时间),但具有可以加载两个源的寄存器会增加成本。如果堆栈变得非常大,用可寻址存储器替换推挽寄存器会更便宜。即使为此使用了一个小的专用存储器,拥有 32 个可寻址寄存器和一个具有递增/递减逻辑的 5 位指针寄存器也比拥有 32 个寄存器每个都有两个输入更便宜。如果应用程序可能需要比 CPU 容易容纳的更多堆栈,则可以使用堆栈指针以及逻辑从主 RAM 存储/获取堆栈数据。
堆栈指针是一个小寄存器,用于存储堆栈顶部的地址。它用于指向堆栈顶部的地址。