什么存储在堆上以及存储在堆栈中的内容?

时间:2012-01-02 11:54:15

标签: java c++ c memory-management

任何人都可以用C,C ++和Java清楚地解释。 什么都在堆栈上,什么都在堆上,什么时候分配完成。

据我所知,

每个函数调用的基元,指针或引用变量的所有局部变量都在新的堆栈帧上。

使用new或malloc创建的任何东西都会堆积。

我对一些事感到困惑。

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

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

6 个答案:

答案 0 :(得分:35)

内存中程序的结构

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

 +--------------------------+
 |                          |
 |      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 [带符号的块开始])
  • 代码段
  • 堆叠和堆区域

数据细分

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

数据段的另一部分称为BSS(因为旧的IBM系统将该段初始化为零)。它是内存的一部分,OS将内存块初始化为零。这就是未初始化的全局数据和静态获取默认值为零的方式。这个区域是固定的,有静态尺寸。

基于显式初始化,数据区域被分成两个区域,因为要初始化的变量可以逐个初始化。但是,未初始化的变量不需要用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 :(得分:5)

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

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

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

答案 2 :(得分:4)

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

答案 3 :(得分:2)

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

对象中的基元和引用位于堆上(因为对象在堆上)

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

将唯一的字符串文字添加到堆中。原始文字可能在某个地方的代码中(如果没有被优化掉)是否一个字段是静态的,也没有区别。

答案 4 :(得分:2)

Section 3.5

Java Virtual Machine Specification描述了运行时数据区(堆栈和堆)。

C和C ++语言标准都没有指定是否应该将某些内容存储在堆栈或堆中。它们只定义对象的生命周期,可见性和可修改性;由实现将这些需求映射到特定平台的内存布局。

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

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

答案 5 :(得分:1)

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

我首先要说的是,没有new的对象被创建为堆栈中的连续单元,或者在某个全局段(特定于平台)中是全局的。

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

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