ARM:什么是满减栈?为何选择满减栈?
栈的4种方式(以下以满减为主)
1. 满减栈的定义
-
满(Full):栈指针SP始终指向最后一个压入栈的有效数据(即栈顶元素)。
-
减(Descending):栈向内存低地址方向增长(栈空间从高地址向低地址扩展)。
2. 压栈操作
当数据被压入栈时:
-
先递减SP:SP的值先减去操作所需的内存大小(例如,32位系统每次减4字节)。
-
再存储数据:将数据写入SP指向的新地址。
3. 弹栈操作
当数据从栈中弹出时:
-
先读取数据:从SP当前指向的地址加载数据。
-
再递增SP:SP的值加上相应的内存大小。
4. 硬件指令的支持
ARM通过以下两类指令显式支持满减栈:
a. 多寄存器操作指令
-
压栈:
STMDB SP!
(Store Multiple Decrement Before)STMDB SP!, {R0-R3} ; SP -= 16 → 依次存储R3, R2, R1, R0到新地址
-
STMDB
(递减存储)会在存储前递减SP,符合“满减”规则。 -
!
表示更新SP的值。
-
-
弹栈:
LDMIA SP!
(Load Multiple Increment After)LDMIA SP!, {R0-R3} ; 依次加载R0, R1, R2, R3 → SP += 16
-
LDMIA
(递增加载)在加载后递增SP,符合“先读后增”逻辑。
-
b. 专用栈指令(Thumb模式)
在Thumb-2指令集中,PUSH
和POP
指令进一步简化操作:
PUSH {R0, R1} ; SP -= 8 → 存储R1, R0到[SP]和[SP+4] POP {R0, R1} ; 加载[SP]到R0, [SP+4]到R1 → SP += 8
5. 为何选择满减栈?
-
ARM ABI规范:AAPCS(ARM Architecture Procedure Call Standard)规定使用满减栈作为标准调用约定。
-
硬件效率:递减存储(STMDB)和递增加载(LDMIA)的组合可以高效处理连续内存操作,且与CPU流水线优化兼容。
-
自然向下扩展:栈从高地址向低地址增长,与堆(Heap)从低地址向高地址扩展形成互补,避免内存区域重叠。
-
调试友好:栈溢出时,SP指向低地址的非法区域,便于调试工具检测(如MMU异常)。
6. 实际内存变化示例
假设初始时 SP = 0x2000_1000
:
-
压栈R0(值=0x1234):
-
SP -= 4
→SP = 0x2000_0FFC
-
存储
0x1234
到地址0x2000_0FFC
。
-
-
再次压栈R1(值=0x5678):
-
SP -= 4
→SP = 0x2000_0FF8
-
存储
0x5678
到地址0x2000_0FF8
。
-
-
弹栈到R2:
-
读取
0x2000_0FF8
→R2 = 0x5678
-
SP += 4
→SP = 0x2000_0FFC
-
总结
ARM通过硬件指令的寻址模式(如STMDB
/LDMIA
或STMFD
/LDMFD
)强制SP在压栈时先减后存、弹栈时先取后增,从而实现了“满减”栈。这种机制由架构直接支持,确保了栈操作的原子性和高效性,同时符合ARM标准调用约定(AAPCS)。
额外注意:
一般来说栈(stack)从高地址向低地址延申,堆(heap)从低地址向高地址延申,他们从两端向中间生长。