堆栈,堆和动态内存分配

时间:2018-12-17 04:26:02

标签: c++ operating-system dynamic-memory-allocation

我对这三个概念有些困惑:堆栈,堆和动态内存分配。我将用C ++提供示例。

  1. 我是说一个程序,对于它的所有变量,数组,也许还有堆栈上的对象,当程序刚启动时,所需的所有存储空间已经存在,所以所有的东西都是预先确定的吗?但是在程序运行时,对我来说,它仍然听起来像“动态的”,因为堆栈仍在变化,从某种意义上说,值仍然在运行时被压入堆栈,弹出堆栈。

  2. 关于堆,出于“动态”的意义,我从这个站点上的一些答案中得到了这个想法,它是针对运行时确定的:

    cin >> box_size;
    int *box = new int[box_size];
    

    但是,接下来呢

    Box surprise_box = new Box();
    

    我已经知道编译时需要多少空间了吗?但是它仍然在堆上。这样看来,“动态”内存分配正好成为程序员分配/分配内存的负担之一。

†:应该改为Box *ptr_surprise_box = new Box();。 (感谢您的评论)


我理解为什么我的问题被认为过于笼统,但是我不知道如何将它们分成几部分。更糟糕的是,现在我正在考虑这是否与地址空间以及在编译时确定的内存地址有关。

4 个答案:

答案 0 :(得分:4)

  

我对这三个概念有些困惑:堆栈,堆和动态内存分配。

您当然是在混合不同领域的概念-前2个与OS相关,最后与语言相关。

对于C ++,没有诸如堆栈或堆之类的东西。 C ++中的对象有4种不同的存储期限:自动,静态,线程和动态。是的,具有自动存储时间的对象通常存储在堆栈中,而具有动态存储时间的对象通常存储在堆中,但这就是实现的细节-从语言的角度来看,没有这种东西。有关存储期限的详细信息,请参见here

答案 1 :(得分:3)

我发现查看堆与堆栈的最好方法不是“动态”或“在编译时知道大小”(尽管这可能是因素)。相反,最好从生命周期管理的角度来看它们。

当超出范围时,分配在堆栈上的东西将被销毁。在堆上分配的内容将一直存在,直到使用deletefreedelete[](取决于它们的分配方式)显式释放它们为止。

要回答您的问题:

  

我正确地说,对于一个程序,对于它的所有变量,   程序刚启动时的数组,甚至可能是堆栈上的对象,   所有需要的存储空间已经存在,因此一切   预定的。

嗯,在每个功能的基础上,有点...,但是这种观点并不能真正说明函数可以执行诸如调用自身(递归)之类的操作并到达任意堆栈的事实。大小用法。

  

因此,“动态”内存分配似乎正好成为程序员分配/分配内存的负担之一。

是的,根本的区别实际上在于生命周期管理。

答案 2 :(得分:2)

  1. 一般来说,是的。在程序开始时,调用函数时,大多数数据都在堆栈上,然后可以在堆上分配内存。 (嗯,程序中不仅有stackheap,在其他段中也可能有全局变量,这是一个很长的故事。)

堆栈空间是由编译器分配的,当您输入函数时,编译器生成的代码会自动为堆栈变量分配足够的空间。

堆空间由您分配,当您需要一些内存时,可以调用一个函数为您分配堆空间。

  1. 指向内存地址的指针是堆栈上的变量

输入您的代码

cin >> box_size;
int *box = new int[box_size];

这里box是堆栈上的变量。 box变量的值是指向堆上内存的指针。

但是Box surprise_box = new Box();无效的语法。

示例:

  • Box box1;:变量box1在堆栈中。

  • Box *box2 = new Box();box2的变量在堆栈上,它是一个指针,指向new Box()的内存

已更新:

没有关于“堆”和“堆”概念的明确的“动态”定义。例如,在C99中,如果您执行scanf("%d", &size); int a[size];仍然可以,并且a可以在堆栈上,而不是堆上。这完全取决于编译器的行为。您分配的内存通常在堆上(您称为内存分配函数,包括new()),编译器代码分配的内存通常在堆栈上。

ps:我认为@Slava的答案是相当不错的。这些概念在不同的领域。 stackheap主要与操作系统相关,dynamicstatic主要与语言相关。我刚刚谈到了现代C ++语言的大多数实现,它们碰巧将new()内存放入堆中,等等。

答案 3 :(得分:1)

用户slava提到了这些:

  

automaticstaticthreaddynamic

除了thread之外,我将重点关注其他所有内容。他还这样说:

  

对于C ++,没有诸如堆栈或堆之类的东西


用户Even Teran说过:

  

我发现查看堆与堆栈的最好方法不是“动态”或“在编译时知道大小”(尽管这可能是因素)。相反,最好从生命周期管理的角度来看它们。

     

当超出范围时,分配在堆栈上的东西将被销毁。在堆上分配的内容将一直存在,直到使用delete,free或delete [](取决于它们的分配方式)显式释放它们为止。


我将通过演示将先前的两个答案合并为一个答案:


  • 1 st -Automatic Storage

int main () {
    int x = 5;

    { // new scope
        int x = 7;
        std::cout << x << '\n';  // prints 7     
    } // end scope

    std::cout << x << '\n';  // prints 5

    return 0;
}

这被视为Automatic Storage,因为变量声明和内存一旦超出范围就会被销毁。从某种意义上来说,您可以将其视为stack变量,因为在C ++中,每个function都有一个stack frame。以上两个变量x都位于main.cpp的堆栈框架中,但具有automatic storage。但是,对std::cout的第一次调用将打印7,因为它在声明为x的2 nd 变量的同一范围内。一旦到达closing大括号},此作用域就会被破坏,第二个 nd x变量也会被破坏。因此,当我们到达下一个对std::cout的调用时,它将打印5,因为它在声明的x的1 st 的同一范围内。


  • 2 nd -Dynamic Storage

int main() {
   int x = 5;  // Automatic storage
   int* ptr = nullptr; // raw pointer

   ptr = new int(x); // Dynamic Storage
   std::cout << "x = " << x << '\n';        // prints 5
   std::cout << "*ptr = " << *ptr << '\n';  // prints 5

   { // new scope
       *ptr = 12;
   } // end scope

   std::cout << "x = " << x << '\n';        // x unchanged prints 5
   std::cout << "*ptr = " << *ptr << '\n';  // prints 12

   delete ptr; // clean up memory

   return 0;
}

Dynamic Storage的寿命比声明的作用域更长。您可以将动态内存从一个函数传递到另一个函数,只要该函数所引用的原始对象仍然有效,否则就会发生内存泄漏,无效或悬空的指针可能导致不良行为,不确定行为,程序崩溃,操作系统被擦除以及核弹爆炸! (最后一点有点戏剧性;但是不要笑,因为糟糕的内存管理将对您的代码库造成严重破坏,所有WHO都将使用它!)。这使您可以存储数据并将其从一个功能修改为另一个功能,而不必每次在其他计算中引用它时都在其副本上创建副本。


  • 3 rd -Static Storage

static int i = 0; // static storage: life time of application or file scope; similar to a global...

void add1() {
   i++;
}

int main() {
    std::cout << i << '\n';
    add1();
    std::cout << i << '\n';

    for ( int n = 0; n < 10; n++ ) {
        add1();
        std::cout << "n=" << n << ": " << i << '\n';
    }

    return 0;
}

输出

0
1
n=0: 2
n=1: 3
n=2: 4
n=3: 5
n=4: 6
n=5: 7
n=6: 8
n=7: 9
n=8: 10
n=9: 11

Static Storage有所不同。不需要像动态内存一样对其进行清理,因为它具有与Automatic Storage类似的属性,因为它将被自动销毁。但是,它们通常在global namespaceglobal file space中可见,它们很有用,但如果管理不当或使用不当,则全局变量又可能很危险。如果在main.cpp中定义,则它们通常具有程序应用程序的生存期,如果在其他cpp文件中定义,则它们具有文件作用域的生存期。与Static Storage的唯一不同之处在于,它也只初始化一次,而且通常只有一个实例!


是的,存在不同类型的存储类,并且许多存储类仍将它们称为stackheap,这主要是由于C++是基于C构建的;但是多年来的含义和用法已经发生了巨大变化。在C++中,与位于内存中的where相比,它与变量的生存期和可见性有关。