哪些内容存储在堆上,哪些内容存储在堆栈上?[已关闭]

2022-09-01 16:50:17

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

据我所知,

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

用新的或malloc创建的任何东西都会堆积如山。

我对几件事感到困惑。

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

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


答案 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 [块以符号开头])
  • 代码段
  • 堆栈和堆区域

数据段

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

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

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

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

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

代码段

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

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

尝试写入代码区域会导致未定义的行为。例如(我将仅提供基于示例的示例),以下代码可能会导致运行时错误甚至使系统崩溃。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);
}

希望这有帮助!


答案 2

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

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

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