assembly 帧指针不是使堆栈指针冗余吗?

lzfw57am  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(138)

据我所知,堆栈指针指向堆栈上的“空闲”内存,而“推送”堆栈上的数据写入堆栈指针所指向的位置,并递增/递减它。
但是,是否可以使用帧指针的偏移量来实现同样的目的,从而节省寄存器。向帧指针添加偏移量的开销与递增和递减堆栈指针的开销几乎相同。我看到的唯一优点是从“顶部”访问数据。(或底部)将较快,只要其不是推入或弹出操作,例如,仅阅读或写入到所述地址而不递增/递减。但话又说回来,此类操作将使用帧指针花费单个额外循环,并且将有一个附加寄存器用于通用用途。
看起来只有帧指针是真正需要的。而且它甚至比修改当前堆栈帧中的数据有更多的用途,比如在调试和堆栈展开中使用。我是否遗漏了什么?

yb3bgrhw

yb3bgrhw1#

是的,实际上对于64位代码生成器来说是很常见的。然而,有些复杂的情况并不能使它成为普遍的可能。一个硬性要求是在编译时知道堆栈指针的值,这样代码生成器就可以可靠地生成偏移量。在以下情况下,这是行不通的:

  • 语言运行库提供了重要的对齐保证。当堆栈帧包含8字节变量(如 double)时,在32位代码中尤其会出现问题。访问未对齐的变量的开销非常大(x2如果未对准4,x3,如果它跨越L1高速缓存线的话),并且可能使存储器模型保证无效。代码生成器通常无法假定函数是以对齐的堆栈进入的,因此需要在函数序言中生成代码,这可能导致堆栈指针额外递减4个字节。
  • 语言运行时为程序提供了一种动态分配堆栈空间的方法。2这是一种非常常见和理想的方法,它是一种非常便宜和快速的内存。3例如CRT中的alloca(),C99+中的可变长度数组,C#语言中的 stackalloc 关键字。
  • 语言运行库需要提供一种可靠的方法来遍历堆栈。这在异常处理、需要能够发现调用方权限的沙盒实现、需要能够发现指向对象的指针的垃圾收集语言中很常见。当然,有许多可能的方法可以做到这一点,但使用基指针并将调用方的基指针存储在堆栈帧中的已知位置会使它变得简单。
7rfyedvj

7rfyedvj2#

你的问题应该是:帧指针是否冗余?
在大多数情况下,可以在大多数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本身将在堆栈指针指向的地址存储信息,而不可能改变这种行为!

相关问题