【问题标题】:What is the direction of stack growth in most modern systems?大多数现代系统中堆栈增长的方向是什么?
【发布时间】:2010-10-14 11:10:02
【问题描述】:

我正在准备一些 C 语言培训材料,我希望我的示例适合典型的堆栈模型。

C 堆栈在 Linux、Windows、Mac OSX(PPC 和 x86)、Solaris 和最新的 Unix 中的发展方向是什么?

【问题讨论】:

标签: assembly stack callstack abi stack-frame


【解决方案1】:

堆栈在 x86 上向下增长(由架构定义,pop 递增堆栈指针,push 递减。)

【讨论】:

    【解决方案2】:

    堆栈的增长通常不取决于操作系统本身,而是取决于运行它的处理器。例如,Solaris 在 x86 和 SPARC 上运行。 Mac OSX(如您所述)在 PPC 和 x86 上运行。 Linux 可以在任何东西上运行,从我的工作中的大 honkin' System z 到 puny little wristwatch

    如果 CPU 提供任何类型的选择,操作系统使用的 ABI / 调用约定指定如果您希望您的代码调用其他所有人的代码,您需要做出哪种选择。

    处理器及其方向是:

    • x86:向下。
    • SPARC:可选。标准 ABI 使用 down。
    • PPC:我认为是。
    • System z:在链表中,我不骗你(但仍然失败,至少对于 zLinux)。
    • ARM:可选择,但 Thumb2 具有仅向下的紧凑编码(LDMIA = 之后递增,STMDB = 之前递减)。
    • 6502:下降(但只有 256 个字节)。
    • RCA 1802A:任意方式,取决于 SCRT 实施情况。
    • PDP11:关闭。
    • 8051:向上。

    在最后几台上显示我的年龄,1802 是用于控制早期穿梭机的芯片(我怀疑,根据它的处理能力来感应门是否打开 :-) 和我的第二台计算机,@ 987654322@(关注我的ZX80)。

    PDP11 详细信息来自 here,8051 详细信息来自 here

    SPARC 架构使用滑动窗口寄存器模型。架构上可见的细节还包括寄存器窗口的循环缓冲区,这些缓冲区在内部有效并缓存,在溢出/下溢时带有陷阱。有关详细信息,请参阅here。正如the SPARCv8 manual explains,SAVE 和 RESTORE 指令就像 ADD 指令加上寄存器窗口旋转。使用正常数而不是通常的负常数会产生向上增长的堆栈。

    上述 SCRT 技术是另一种技术 - 1802 使用了一些或它的 16 个 16 位寄存器用于 SCRT(标准调用和返回技术)。一个是程序计数器,您可以通过SEP Rn 指令将任何寄存器用作PC。一个是堆栈指针,两个被设置为始终指向 SCRT 代码地址,一个用于调用,一个用于返回。 没有寄存器被以特殊方式处理。请记住,这些细节来自记忆,它们可能并不完全正确。

    例如,如果 R3 是 PC,R4 是 SCRT 调用地址,R5 是 SCRT 返回地址,R2 是“堆栈”(引用,因为它是在软件中实现的),SEP R4 会将 R4 设置为PC 并开始运行 SCRT 调用代码。

    然后它将 R3 存储在 R2“堆栈”上(我认为 R6 用于临时存储),向上或向下调整它,抓取 R3 后面的两个字节,将它们加载到 R3,然后执行SEP R3 并在新地址运行。

    要返回,它会 SEP R5 将旧地址从 R2 堆栈中拉出,向其添加两个(以跳过调用的地址字节),将其加载到 R3 和 SEP R3 以开始运行前一个代码。

    在完成所有基于 6502/6809/z80 堆栈的代码之后,最初很难理解,但仍然以一种撞墙的方式优雅。此外,该芯片的一大卖点是一整套 16 个 16 位寄存器,尽管您立即丢失了其中的 7 个(5 个用于 SCRT,2 个用于 DMA 和内存中断)。啊,营销对现实的胜利:-)

    System z 实际上非常相似,使用它的 R14 和 R15 寄存器进行调用/返回。

    【讨论】:

    • 要添加到列表中,ARM 可以在任一方向上增长,但可以通过特定的硅实现设置为一个或另一个(或者可以通过软件进行选择)。我处理过的少数人一直处于成长模式。
    • 在我目前所见的一小部分 ARM 世界 (ARM7TDMI) 中,堆栈完全由软件处理。返回地址存储在一个寄存器中,如果需要,该寄存器由软件保存,并且前/后递增/递减指令允许将其和其他内容以任一方向放在堆栈中。
    • 一个HPPA,堆栈长大了!在相当现代的建筑中相当罕见。
    • 对于好奇的人,这里有一个关于堆栈如何在 z/OS 上工作的好资源:www-03.ibm.com/systems/resources/Stack+and+Heap.pdf
    • 我不确定我对 a stack grows down 的表述是什么(例如,它向东增长)。 “向下”是否意味着堆栈指针的值随着push 操作而减小并随着pop 操作而增加?
    【解决方案3】:

    它变小是因为分配给程序的内存有“永久数据”,即程序本身的代码在底部,然后是堆在中间。您需要另一个固定点来引用堆栈,这样您就可以在顶部。这意味着堆栈会向下增长,直到它可能与堆上的对象相邻。

    【讨论】:

      【解决方案4】:

      在 C++ 中(适用于 C)stack.cc:

      static int
      find_stack_direction ()
      {
          static char *addr = 0;
          auto char dummy;
          if (addr == 0)
          {
              addr = &dummy;
              return find_stack_direction ();
          }
          else
          {
              return ((&dummy > addr) ? 1 : -1);
          }
      }
      

      【讨论】:

      • 哇,好久没看到“auto”关键字了。
      • (&dummy > addr) 未定义。仅当两个指针指向同一个数组或结构时,才定义将两个指针提供给关系运算符的结果。
      • 试图调查你自己的堆栈的布局——C/C++ 根本没有指定的东西——一开始是“不可移植的”,所以我不会真正关心这一点。不过,看起来这个功能只能正常工作一次。
      • 没有必要为此使用static。相反,您可以将地址作为参数传递给递归调用。
      • 另外,通过使用static,如果您多次调用此函数,后续调用可能会失败...
      【解决方案5】:

      向下增长的优势在于,在旧系统中,堆栈通常位于内存的顶部。程序通常从底部开始填充内存,因此这种内存管理最大限度地减少了测量堆栈底部并将其放置在合理位置的需要。

      【讨论】:

      • 不是“优势”,真的是重言式。
      • 不是重言式。正如@valenok 指出的那样,关键是要有两个不断增长的内存区域不会相互干扰(除非内存已满)。
      【解决方案6】:

      在 MIPS 和许多现代 RISC architectures(如 PowerPC、RISC-V、SPARC...)中,没有 pushpop 指令。这些操作是通过手动调整堆栈指针显式完成的,然后相对于调整后的指针加载/存储值。所有寄存器(除了零寄存器)都是通用的,所以理论上任何寄存器都可以是堆栈指针,堆栈可以在程序员想要的任何方向增长

      也就是说,在大多数架构上,堆栈通常会向下增长,这可能是为了避免堆栈和程序数据或堆数据增长并相互冲突的情况。还有sh-'s answer提到的重要寻址原因。一些例子:MIPS ABI 向下增长并使用$29 (A.K.A $sp) 作为堆栈指针,RISC-V ABI 也向下增长并使用 x2 作为堆栈指针

      在 Intel 8051 中,堆栈变大了,可能是因为内存空间太小(原始版本为 128 字节)以至于没有堆,并且您不需要将堆栈放在顶部以便将其与从底部增长的堆

      您可以在https://en.wikipedia.org/wiki/Calling_convention中找到有关各种架构中堆栈使用的更多信息

      另见

      【讨论】:

        【解决方案7】:

        在大多数系统上,堆栈会增长,我在https://gist.github.com/cpq/8598782 的文章解释了它增长的原因。很简单:如何在一块固定的内存中布局两个不断增长的内存块(堆和栈)?最好的解决办法是把它们放在相反的两端,让它们相互生长。

        【讨论】:

        • 那个要点现在似乎已经死了:(
        • @Ven - 我可以搞定
        【解决方案8】:

        这个宏应该在没有 UB 的情况下在运行时检测到它:

        #define stk_grows_up_eh() stk_grows_up__(&(char){0})
        _Bool stk_grows_up__(char *ParentsLocal);
        
        __attribute((__noinline__))
        _Bool stk_grows_up__(char *ParentsLocal) { 
            return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
        }
        

        【讨论】:

          【解决方案9】:

          只是对其他答案的一个小补充,据我所知,这还没有涉及到这一点:

          堆栈向下增长使堆栈内的所有地址相对于堆栈指针都有一个正偏移。不需要负偏移,因为它们只会指向未使用的堆栈空间。当处理器支持堆栈指针相对寻址时,这会简化对堆栈位置的访问。

          许多处理器的指令允许使用相对于某个寄存器的仅正偏移量进行访问。其中包括许多现代建筑,以及一些古老的建筑。例如,ARM Thumb ABI 为堆栈指针相关的访问提供了在单个 16 位指令字中编码的正偏移量。

          如果堆栈向上增长,所有相对于堆栈指针的有用偏移量都将为负数,这不太直观且不太方便。它也与寄存器相对寻址的其他应用程序不一致,例如访问结构体的字段。

          【讨论】:

            猜你喜欢
            • 2020-06-15
            • 2011-04-04
            • 2011-03-23
            • 2020-11-01
            • 2021-04-14
            • 2011-07-18
            • 2012-07-30
            相关资源
            最近更新 更多