帧指针不会使堆栈指针冗余吗?

时间:2014-10-09 12:13:37

标签: pointers assembly stack frame

据我了解,堆栈指针指向堆栈上的“空闲”内存,堆栈上的“推送”数据写入堆栈指针指向的位置并递增/递减它。

但是不可能使用帧指针的偏移来实现相同的功能,从而节省了寄存器。将偏移量添加到帧指针的开销与递增和递减堆栈指针的开销几乎相同。我看到的唯一优势是从“顶部”(或底部)访问数据会更快,只要它不是推动或弹出操作,例如只是读取或写入该地址而不递增/递减。但话说回来,这样的操作将使用帧指针进行一个额外的循环,并且将有一个额外的寄存器用于通用目的。

似乎只需要帧指针。它甚至比仅修改当前堆栈帧中的数据更有用,例如用于调试和堆栈展开。我错过了什么吗?

2 个答案:

答案 0 :(得分:4)

嗯,是的,实际上对于64位代码生成器来说很常见。然而,有些并发症并不能普遍实现。硬盘要求是在编译时已知堆栈指针的值,因此代码生成器可以可靠地生成偏移量。这在以下情况下不起作用:

  • 语言运行时提供了非平凡的对齐保证。特别是当堆栈帧包含8字节变量时,32位代码存在问题,如 double 。访问错误对齐的变量非常昂贵(x2如果未对齐4,如果它跨越L1缓存行则为x3)并且可能使内存模型保证无效。代码生成器通常不能假定函数是使用对齐的堆栈进入的,因此需要在函数序言中生成代码,这可能导致堆栈指针减少额外的4个字节。

  • 语言运行库为程序提供了一种动态分配堆栈空间的方法。非常普遍和可取,它是非常便宜和快速的记忆。示例包括CRT中的alloca(),C99 +中的可变长度数组,C#语言中的 stackalloc 关键字。

  • 语言运行时需要提供一种可靠的方式来遍历堆栈。常见于异常处理,需要能够发现调用者权限的沙箱的实现,需要能够发现指向对象的指针的垃圾收集语言。当然,有许多可行的方法,但使用基指针并将调用者的基指针存储在堆栈框架中的已知位置使其变得简单。

答案 1 :(得分:2)

您的问题应该是:帧指针是否冗余?

在大多数情况下,只能使用堆栈指针编写代码,而不是在大多数CPU上使用帧指针(某些CPU,如16位模式下的x86,限制访问堆栈指针,因此帧指针是必需的)。

一个例子:

mov ebp, esp
push esi
mov eax, [ebp+4]
push edi
mov eax, [ebp+8]

也可以写成:

push esi
mov eax, [esp+8]
push edi
mov eax, [esp+16]

一些特殊情况 - 比如alloca()函数 - 但是需要同时使用帧和堆栈指针。

然而,堆栈指针永远不会冗余:

您必须考虑中断使用堆栈指针。中断是操作系统功能,当满足某些条件时(例如,已收到来自USB端口的电信号),硬件(而不是CALL指令)自动调用这些功能。

因为这样的中断假设堆栈指针下面的内存是空闲的,所以在堆栈指针下面使用内存是一个非常糟糕的主意。如果发生中断,则堆栈指针下方的内存将被破坏!

在MIPS CPU上(例如),它是纯粹的约定,哪个寄存器是堆栈指针;你也可以说R9是堆栈指针,堆栈不是在地址R9,而是在地址R9 + 1234。 64位Sparc调用约定对堆栈指针使用了这种奇怪的约定。但是,这要求所有代码(包括操作系统和所有中断)使用相同的约定。

在x86 CPU上这是不可能的,因为CPU本身会假设堆栈指针下面的内存是空闲的:PUSH和CALL指令将写入堆栈指针下面的内存,如果发生中断,CPU本身将将信息存储在堆栈指针指向的地址,而不可能改变这种行为!