【问题标题】:What is stored on heap and what is stored on stack? [closed]什么存储在堆上,什么存储在堆栈上? [关闭]
【发布时间】:2012-01-31 19:08:32
【问题描述】:

谁能用 C、C++ 和 Java 清楚地解释一下。 堆栈上发生了什么,堆上发生了什么,分配何时完成。

据我所知

每个函数调用的所有局部变量,无论是基元、指针还是引用变量,都在一个新的堆栈帧上。

任何用 new 或 malloc 创建的东西都在堆上。

我对一些事情感到困惑。

作为在堆上创建的对象成员的引用/原语是否也存储在堆上?

以及在每个帧中递归创建的方法的那些本地成员呢? 它们都在堆栈上吗,如果是,那么堆栈内存是在运行时分配的吗? 同样对于文字,它们是代码段的一部分吗? 以及 C 中的全局变量、C++/Java 中的静态变量和 C 中的静态变量。

【问题讨论】:

  • 局部变量不一定在栈帧中——它们可能只存在于寄存器中,甚至可能被完全优化掉。
  • 理想情况下,这些都是实现细节。你在比较什么实现。
  • 标准实现 Sun/Oracle 实现的 JDK 实现。对于 C/C++,我说的是 gcc。
  • 如果您只是在搜索框中输入了您的主题,您会看到很多关于这个问题的现有答案。事实上,这些答案应该在您输入问题时出现。

标签: java c++ c memory-management


【解决方案1】:

内存中程序的结构

以下是任何程序在内存中加载时的基本结构。

 +--------------------------+
 |                          |
 |      command line        |
 |        arguments         |
 |    (argc and argv[])     |
 |                          |
 +--------------------------+
 | Stack                    |
 | (grows-downwards)        |
 |                          |
 |                          |
 |                          |
 |         F R E E          |
 |        S P A C E         |
 |                          |
 |                          |
 |                          |
 |                          |
 |     (grows upwards) Heap |
 +--------------------------+
 |                          |
 |    Initialized data      |
 |         segment          |
 |                          |
 +--------------------------+
 |                          |
 |     Initialized to       |
 |        Zero (BSS)        |
 |                          |
 +--------------------------+
 |                          |
 |      Program Code        |
 |                          |
 +--------------------------+

注意几点:

  • 数据段
    • 初始化数据段(由程序员初始化为显式初始化器)
    • 未初始化的数据段(初始化为零的数据段 - BSS [Block Start with Symbol])
  • 代码段
  • 堆栈和堆区域

数据段

数据段包含由包含初始化值的用户显式初始化的全局和静态数据。

数据段的另一部分称为 BSS(因为旧的 IBM 系统将该段初始化为零)。它是操作系统将内存块初始化为零的内存部分。这就是未初始化的全局数据和静态如何将默认值设为零。这个区域是固定的,大小是静态的。

数据区根据显式初始化分为两个区域,因为要初始化的变量可以一个一个地初始化。但是,未初始化的变量不需要用 0 的一一显式初始化。取而代之的是,初始化变量的工作留给了操作系统。这种批量初始化可以大大减少加载可执行文件所需的时间。

大部分数据段的布局都在底层操作系统的控制之下,仍然有一些加载器将部分控制权交给了用户。此信息在嵌入式系统等应用程序中可能很有用。

可以使用代码中的指针来寻址和访问该区域。自动变量在每次需要它们时都会在初始化变量时产生开销,并且需要代码来执行该初始化。但是,数据区中的变量没有这种运行时重载,因为初始化只进行一次,而且在加载时也是如此。

代码段

程序代码是可执行代码可供执行的代码区域。这个区域也是固定大小的。这只能通过函数指针访问,而不能通过其他数据指针访问。这里需要注意的另一个重要信息是系统可能会将此区域视为只读内存区域,并且任何尝试写入该区域都会导致未定义的行为。

常量字符串可以放置在代码或数据区域中,这取决于实现。

尝试写入代码区域会导致未定义的行为。例如(我将只给出基于C 的示例)以下代码可能会导致运行时错误甚至系统崩溃。

int main()
{
    static int i;
    strcpy((char *)main,"something");
    printf("%s",main);
    if(i++==0)
    main();
}

堆栈和堆区域

对于执行,程序使用两个主要部分,堆栈和堆。堆栈帧是在堆栈中为函数创建的,在堆中为动态内存分配创建。堆栈和堆是未初始化的区域。因此,内存中发生的任何事情都会成为在该空间中创建的对象的初始(垃圾)值。

让我们看一个示例程序来显示哪些变量存储在哪里,

int initToZero1;
static float initToZero2;
FILE * initToZero3; 
// all are stored in initialized to zero segment(BSS)

double intitialized1 = 20.0;
// stored in initialized data segment

int main()
{
    size_t (*fp)(const char *) = strlen;
    // fp is an auto variable that is allocated in stack
    // but it points to code area where code of strlen() is stored

    char *dynamic = (char *)malloc(100);
    // dynamic memory allocation, done in heap

    int stringLength;
    // this is an auto variable that is allocated in stack

    static int initToZero4; 
    // stored in BSS

    static int initialized2 = 10; 
    // stored in initialized data segment   

    strcpy(dynamic,”something”);    
    // function call, uses stack

    stringLength = fp(dynamic); 
    // again a function call 
}

或者考虑一个更复杂的例子,

// command line arguments may be stored in a separate area  
int main(int numOfArgs, char *arguments[])
{ 
    static int i;   
    // stored in BSS 

    int (*fp)(int,char **) = main;  
    // points to code segment 

    static char *str[] = {"thisFileName","arg1", "arg2",0};
    // stored in initialized data segment

    while(*arguments)
        printf("\n %s",*arguments++);

    if(!i++)
        fp(3,str);
}

希望这会有所帮助!

【讨论】:

  • 当我在程序空间上映射某些东西时,它映射到哪里? +1 这么好的解释
  • @Mr.32 感谢您的 +1。我不确定mmap() 在上面的布局中属于哪里。这是一个很好的问题。我正在尝试同样的探索! OTOH,我试图保持对所有上述编程语言的通用描述。我也应该涵盖registerextern 变量!
【解决方案2】:

在 C/C++ 中: 局部变量分配在当前栈帧上(属于当前函数)。如果您静态分配一个对象,则整个对象都分配在堆栈上,包括其所有成员变量。使用递归时,每个函数调用都会创建一个新的堆栈帧,并且所有局部变量都分配在堆栈上。堆栈通常具有固定大小,该值通常在编译/链接期间写入可执行二进制标头中。然而,这是非常特定于操作系统和平台的,某些操作系统可能会在需要时动态增长堆栈。因为堆栈的大小通常是有限的,所以当您使用深度递归时,有时甚至在静态分配大对象时不使用递归时,您可能会用完堆栈。

堆通常被视为无限空间(仅受可用物理/虚拟内存限制),您可以使用 malloc/new(和其他堆分配函数)在堆上分配对象。当在堆上创建一个对象时,它的所有成员变量都会在其中创建。您应该将对象视为内存的连续区域(该区域包含成员变量和指向虚拟方法表的指针),无论它分配在何处。

文字、常量和其他“固定”内容通常作为另一个段编译/链接到二进制文件中,因此它并不是真正的代码段。通常你不能在运行时从这个段中分配或释放任何东西。然而,这也是特定于平台的,它在不同平台上的工作方式可能不同(例如,iOS Obj-C 代码有很多常量引用直接插入到代码段中,在函数之间)。

【讨论】:

    【解决方案3】:

    至少在 C 和 C++ 中,这是所有实现特定的。标准没有提到“堆栈”或“堆”。

    【讨论】:

      【解决方案4】:

      在 Java 中,局部变量可以在堆栈上分配(除非优化掉)

      对象中的基元和引用在堆上(就像对象在堆上一样)

      在创建线程时预先分配了一个堆栈。它不使用堆空间。 (但是创建线程确实会导致创建线程本地分配缓冲区,这会大大减少可用内存)

      独特的字符串文字被添加到堆中。原始文字可能在代码中的某处(如果没有优化掉) 字段是否是静态的没有区别。

      【讨论】:

      • 如果局部变量是一个对象,栈上只存储指针。 Java 中的堆通常更有条理。一个 JVM 实现维护“年轻”一代(即短期)对象,在某种程度上反映了堆栈概念,但具有管理性。相反,C++ 有时必须克隆堆栈对象。
      • @JoopEggen 自 Java 6 Update 14 Oracle (Sun) JVM 也将能够进行逃逸分析并防止在堆上创建对象。见oracle.com/technetwork/java/javase/6u14-137039.html
      • 逃逸分析有时效果很好,但在很多情况下,您可能认为它应该有效,但实际上并没有。 ;)
      【解决方案5】:

      Java Virtual Machine Specification 中的Section 3.5 描述运行时数据区域(堆栈和堆)。

      C 和 C++ 语言标准均未指定应将某些内容存储在堆栈还是堆中。它们只定义对象的生命周期、可见性和可修改性;将这些需求映射到特定平台的内存布局取决于实现。

      通常,使用*alloc 函数分配的任何内容都驻留在堆上,而auto 变量和函数参数驻留在堆栈上。字符串文字可能存在于“其他地方”(它们必须在程序的生命周期内被分配和可见,但尝试修改它们是未定义的);一些平台使用单独的只读内存段来存储它们。

      请记住,有一些真正奇怪的平台可能不符合常见的堆栈堆模型。

      【讨论】:

        【解决方案6】:

        回答关于 C++ 堆和堆栈的部分问题:

        首先我应该说,一个没有 new 创建的对象作为一个连续单元存储在堆栈上,或者如果是全局存储在某个全局段(特定于平台)中。

        对于在堆上使用 new 创建的对象,其成员变量存储为堆上的一个连续内存块。对于基元和嵌入对象的成员变量就是这种情况。在成员变量是指针和引用类型成员变量的情况下,原始指针值存储在对象中。该值指向的内容可以存储在任何地方(堆、堆栈、全局)。一切皆有可能。

        对于对象方法中的局部变量,它们存储在堆栈中,而不是存储在堆上的对象连续空间中。堆栈通常在运行时创建为固定大小。每个线程有一个。局部变量甚至可能不会消耗堆栈上的空间,因为它们可能会被优化出来(如保罗所说)。主要的一点是它们不在堆上,只是因为它们是堆上对象的成员函数。如果它们是指针类型的局部变量,它们可以存储在堆栈中并指向堆或堆栈上的某些内容!

        【讨论】:

          猜你喜欢
          • 2012-11-18
          • 1970-01-01
          • 2013-07-28
          • 1970-01-01
          • 1970-01-01
          • 2021-04-15
          • 2013-12-06
          • 2013-11-08
          • 2019-04-01
          相关资源
          最近更新 更多