【问题标题】:If the program counter points to the address of the next instruction to be executed, what do frame pointers do?如果程序计数器指向下一条要执行的指令的地址,那么帧指针的作用是什么?
【发布时间】:2010-12-30 08:42:46
【问题描述】:

如果程序计数器指向下一条要执行的指令的地址,那么帧指针的作用是什么?

【问题讨论】:

    标签: c callstack


    【解决方案1】:

    它就像一个更稳定的堆栈指针版本

    一些局部变量和参数的存储空间通常分配在堆栈帧中,只需在函数调用后将堆栈指针弹回其原始级别即可自动释放。

    但是,堆栈指针经常被调整,以便将参数推送到堆栈以用于新的调用级别,并且至少在进入方法时调整一次,以便分配其自己的局部变量。调整堆栈指针还有其他更晦涩的原因。

    所有这些调整都使使用偏移量来获取参数、局部变量以及在某些语言中的中间词法作用域变得复杂。编译器跟踪可能并不难,但如果程序正在被调试,那么调试器(人或程序)也必须跟踪变化的偏移量。

    如果从技术上讲是不必要的开销,更简单的是分配一个寄存器来指向当前帧。在 x86 上,这是 %ebp。在进入函数时,它可能与堆栈指针有固定的关系。

    除了调试之外,这还简化了异常管理,甚至可以通过消除或优化对堆栈指针的一些调整来收回成本。

    您提到了程序计数器,因此值得注意的是,通常帧指针完全是一个软件结构,而不是硬件实现的东西,除非几乎每台机器都可以执行 寄存器 + 偏移 em> 寻址方式。像 x86 这样的一些机器确实以寻址模式和宏指令的形式提供了一些硬件支持,用于创建和恢复帧。但是,有时会发现核心指令更快,并且最终不推荐使用宏操作。

    【讨论】:

      【解决方案2】:

      这不是一个真正的 C 问题,因为它完全依赖于编译器。

      但是,堆栈帧是一种有用的方式来考虑当前函数及其父函数。通常,帧指针指向堆栈上的特定位置(对于给定的堆栈深度),您可以从中定位传入的参数以及局部变量。

      这是一个示例,假设您调用一个函数,该函数接受一个参数并返回 1 和该参数之间的所有数字的总和。 C 代码类似于:

      unsigned int x = sumOf (7);
      : :
      unsigned int sumOf (unsigned int n) {
          unsigned int total = 0;
          while (n > 0) {
              total += n;
              n--;
          }
          return total;
      }
      

      为了调用这个函数,调用者将 7 压入堆栈然后调用子程序。函数本身设置帧指针并为局部变量分配空间,所以你可能会看到代码:

              mov  r1,7            ; fixed value
              push r1              ; push it for subroutine
              call sumOf           ; then call
      retLoc: mov  [x],r1          ; move return value to variable
      : :
      sumOf:  mov  fp,sp           ; Set frame pointer to known location
              sub  sp,4            ; Allocate space for total.
      : :
      

      此时(在sub sp,4 之后),您有以下堆栈区域:

            +--------+
            | n(7)   |
            +--------+
            | retLoc |
            +--------+
      fp -> | total  |
            +--------+
      sp -> |        |
            +--------+
      

      您可以看到,您可以通过使用帧指针“上方”的地址和帧指针“下方”的局部变量来找到传入的参数。

      该函数可以使用[fp+8] 访问传入的值(7),即fp+8 处的内存内容(在此示例中,每个单元格都是四个字节)。它也可以用[fp-0]访问它自己的局部变量(total),内存的内容是fp-0。我使用了fp-0 命名法,即使减去零没有任何效果,因为其他本地人将具有相应的较低地址,例如fp-4fp-8 等等。

      当您在堆栈中上下移动时,帧指针也会移动,通常在调用函数之前将前一个帧指针压入堆栈,以便在离开该函数时轻松恢复。但是,虽然堆栈指针在函数中可能会剧烈移动,但帧指针通常保持不变,因此您始终可以找到相关变量。

      【讨论】:

        【解决方案3】:

        很好的讨论here,有例子和所有。

        简而言之:FP 指向堆栈上函数框架内的一个固定点(并且在函数执行期间不会改变),因此所有传递的参数和函数的本地(“自动”)变量都可以通过偏移量访问来自 FP(而 SP 可以在函数执行期间更改,而 PC 肯定会更改;-)。

        【讨论】:

        • 哇...堆栈指针?...是程序计数器的同义词吗?
        • 不,堆栈指针 (SP) 与程序计数器 (PC) 永远不是同一个寄存器:PC 指向代码空间(指向“当前指令”或下一条,取决于 CPU ),SP 总是表示“栈顶”。您真的需要在 FP 之前更好地了解 SP——它是一个更基本的概念,更有可能是一个特定的硬件寄存器,并且较少依赖于给定编译器的代码生成策略。
        【解决方案4】:

        通常是返回地址(但有时只是过去最后一个参数,例如)。关键是帧指针在方法的生命周期内是固定的,而堆栈指针可以在执行期间移动。

        这非常依赖于实现(更多的是机器概念,而不是真正的语言概念)。

        从您提供的评论提升到另一个答案:

        哇...堆栈指针?...是程序计数器的同义词吗?

        了解call stack。基本上,调用堆栈存储当前方法的本地数据(局部变量、方法的参数和调用者的返回地址)。堆栈指针指向分配新空间的结构的顶部(通过将堆栈指针移动“更高”)。

        【讨论】:

          【解决方案5】:

          帧指针指向当前帧中的一块内存区域(当前局部函数),一般是指向当前局部函数的返回地址。

          【讨论】:

            【解决方案6】:

            由于没有人对此做出回应,我会试一试。帧指针(如果有内存的话)和堆栈指针一起是堆栈的一部分。堆栈由堆栈帧(有时称为激活记录)组成。堆栈指针指向堆栈的顶部,而帧指针通常指向帧结构中的某个固定点,例如返回地址的位置。维基百科上有更详细的描述和图片。

            link text

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2014-07-28
              • 1970-01-01
              • 1970-01-01
              • 2019-01-31
              • 2023-03-28
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多