【问题标题】:Is runtime stack kept in data segment of memory?运行时堆栈是否保存在内存的数据段中?
【发布时间】:2010-10-22 14:32:33
【问题描述】:

我对堆栈内存组织非常好奇,因为我实验了后台发生的事情,并且显然看到它与我从书本中获得的微小知识相匹配。只是想检查一下我的理解是否正确。

我有一个基本程序——有 2 个函数,第一个是 foo,另一个是 main(入口点)。

void foo(){
    // do something here or dont
}

int main(){

    int i = 0;

    printf("%p %p %p\n",foo, &i, main);

    system("PAUSE");
    return EXIT_SUCCESS;
};

程序的输出如下图所示,main的局部变量i完全位于不相关的位置。 integer 是一个值类型,但是用一个指向 main 的 char * 指针再次检查它并获得类似的结果。

00401390 0022FF44 00401396
Press any key to continue . . .

我主要理解代码和变量被分配到不同的内存段(代码段/数据段)。所以基本上说调用堆栈折叠函数执行的基本信息(它们的局部变量、参数、返回点)并将它们保存在数据段中是正确的吗?

【问题讨论】:

    标签: c++ c memory stack segments


    【解决方案1】:

    在开始时有一点警告:所有这些答案都在一定程度上受到操作系统和硬件架构的影响。 Windows 的处理方式与类 UNIX 语言、实时操作系统和旧的小型系统 UNIX 完全不同。

    但正如@Richie 和@Paul 所说,基本答案是“是的”。当您的编译器和链接器处理完代码时,它在 UNIX 中被分解为所谓的“文本”和“数据”段。 文本段包含指令和一些静态数据;数据段包含数据。

    然后为堆栈和堆空间分配一大块数据段。其他块可以分配给静态或外部数据结构之类的东西。

    所以是的,当程序运行时,程序计数器正忙于从与数据不同的段中获取指令。现在我们进入一些架构依赖关系,但一般来说,如果你有分段内存,你的指令的构造方式是从段中获取一个字节尽可能高效,在旧的 360 架构中,它们有 基址寄存器,在 x86 中,随着地址空间从旧的 8080 到现代处理器而增长,但所有指令都经过了非常仔细的优化,因为正如您可以想象的那样,获取指令及其操作数非常大量使用。

    现在我们开始使用具有虚拟内存和内存管理单元的更现代的架构。现在机器有了特定的硬件,让程序将地址空间视为一个大的平坦地址范围;各个段只需放置在该位虚拟地址空间中。 MMU 的工作是获取一个虚拟地址并将其转换为一个物理地址,包括如果该虚拟地址目前根本不在物理内存中该怎么办。同样,MMU 硬件经过了高度优化,但这并不意味着没有相关的性能成本。但随着处理器变得越来越快,程序越来越大,它变得越来越不重要。

    【讨论】:

      【解决方案2】:

      是的,完全正确。代码和数据存在于内存的不同部分,具有不同的权限。堆栈保存参数、返回地址和本地(“自动”)变量,并与数据一起存在。

      【讨论】:

      • 所以我们一直说的数据段只对运行时栈感兴趣。我已经有一段时间没有搞乱不受管理的世界了,感谢您的澄清。
      【解决方案3】:

      是的。

      假设您的代码存储器是 ROM,而您的数据存储器是 RAM(一种常见的小芯片架构)。然后你会看到堆栈必须在数据内存中。

      【讨论】:

      • 这有点奇怪:在不同的段之间来回切换以获得过程的返回点。这是性能效率最高的架构吗?
      • 来回走动?返回地址在堆栈上,以及所有代码执行。文本段永远不会被直接读取以执行,除非在指向 main 的最初始指令处。
      【解决方案4】:

      好吧,我可以为 SPARC 代言:

      是的。运行程序时,程序会被读取两次(至少在 SPARC 中)。程序被加载到内存中,然后任何数组/堆栈分配都会被加载。在程序的第二次遍历中,堆栈被分配到单独的内存中。

      我不确定基于 CISC 的处理器,但我怀疑它不会有太大变化。

      【讨论】:

        【解决方案5】:

        您的程序表现出未定义的行为,具体是因为:

        • 您未能包含 <stdio.h><cstdio>,具体取决于您将代码编译为的语言
        • printf 和所有可变参数函数都没有能力对其参数进行类型检查。因此,您必须传递正确键入的参数。你真的应该这样做:
        • system() 在范围内没有声明。包括<stdlib.h><cstdlib> 视情况而定。

        把你的代码写成:

           #include <stdio.h>
        
           int main() {
              /* ... */
              printf("%p %p %p\n", (void *)foo, (void *)&i, (void *)main);
              /* ... */
           }
        

        还要注意:

        • void foo() 的定义不是 C 中的原型,而是 C++ 中的。但是,如果您要编写 void foo(void),您将获得两种语言的原型。
        • system() 依赖于实现——您的代码可能无法跨平台运行。

        语言本身(C 或 C++)对如何组织内存没有任何限制。它甚至没有堆栈或堆的概念。这些是由它们认为合适的实现定义的。理想情况下,您应该查阅实现提供的文档,以大致了解它们的作用。

        【讨论】:

        • 它们是在实际实现中添加的,您的回答完全偏离主题。
        • 哪一部分?您是否意识到讨论已损坏的代码毫无意义?
        • @Dirk:foo 的原型是什么? foo 不仅在上面的代码中声明,它是定义的。不需要原型。至于将指针显式转换为void*——这里完全没有必要(尽管可以说更具可读性)。
        • @Konrad:我想我已经相当清楚地解释了我的观点。 foo 的定义不提供原型,即范围内的完整声明。注意空的参数列表。此外,我需要强制转换,因为 varargs 函数不会(在某些情况下除了 gcc)对参数进行类型检查,并且您应该传入正确类型的参数。我的帖子的重点是:就语言而言,对于 OP 的要求,没有一个正确的答案。如果 OP 提供了有关他/她的实施的更多详细信息,则可以得到正确的答案。
        猜你喜欢
        • 2014-02-17
        • 2020-01-10
        • 2013-03-16
        • 1970-01-01
        • 1970-01-01
        • 2016-10-11
        • 2019-09-12
        • 2021-01-25
        • 1970-01-01
        相关资源
        最近更新 更多