为什么不能在堆栈上释放变量?

时间:2013-11-02 23:55:11

标签: c++ c memory stack heap

有问题的语言是C / C ++。

我的教授说当你完成使用它时释放堆上的内存,因为否则你最终会得到无法访问的内存。这样做的问题是你可能最终耗尽了所有内存,而且你无法访问它们。

为什么相同的概念不适用于堆栈?我知道你总是可以访问你在堆栈中使用的内存,但如果你不断创建新的变量,你最终会用完空间吗?那么为什么你不能在堆栈上释放变量来为堆上的新变量腾出空间呢?

我得到编译器释放堆栈上的变量,但那是在变量范围的末尾。它是否也在其范围的末尾释放堆上的变量?如果没有,为什么不呢?

7 个答案:

答案 0 :(得分:7)

动态分配的对象(通俗语言中的“堆对象”)永远不是变量。因此,它们永远不会超出范围。他们不住在任何范围内。您可以处理它们的唯一方法是通过分配时获得的指针

(指针通常分配给变量,但这没有帮助。)

重复:变量具有范围; 对象没有。但是许多对象都是变量。

回答这个问题:你只能释放对象,而不是变量

答案 1 :(得分:1)

关闭的“}”括号的结尾是堆栈“释放”其内存的位置。如果我有:

{
    int a = 1;
    int b = 2;

    {
        int c = 3; // c gets "freed" at this "}" - the stack shrinks
                   // and c is no longer on the stack.
    }
}                  // a and b are "freed" from the stack at this last "}".

你可以认为c在堆栈上比“a”和“b”更高,所以c会在它们之前弹出。因此,每次编写“}”符号时,都会有效地缩小堆栈并“释放”数据。

答案 2 :(得分:1)

  

那么为什么不能在堆栈上释放变量来为堆上的新变量腾出空间呢?

“堆栈分配器”知道的所有信息都是ESP,它是指向堆栈底部的指针。

   N: used
 N-1: used
 N-2: used
 N-3: used <- **ESP**
 N-4: free
 N-5: free
 N-6: free
 ...

这使得“堆栈分配”非常有效 - 只需按分配大小减少ESP,再加上它是地址/缓存友好的。

如果你允许不同大小的任意解除分配 - 将你的“堆栈”变成“堆”,所有相关的额外开销 - ESP是不够的,因为你必须记住哪个空间是解除分配,但不是:

   N: used
 N-1: free
 N-2: free
 N-3: used
 N-4: free
 N-5: used
 N-6: free
 ...

显然 - ESP还不够。而且你还必须处理碎片问题。

  

我得到编译器释放堆栈上的变量,但那是在变量范围的末尾。它是否也在其范围的末尾释放堆上的变量?如果没有,为什么不呢?

其中一个原因是您并不总是希望这样 - 有时您希望将已分配的数据返回给您的函数调用者,该数据应该比创建它的范围更长。

也就是说,如果你真的需要基于范围的生命周期管理“堆”分配数据(并且大部分时间它是基于范围的,确实) - 在C ++中通常的做法是使用这些数据的包装器。其中一个例子是std::vector

{
    std::vector<int> x(1024); // internally allocates array of 1024 ints on heap
    // use x
    // ...
} // at the end of the scope destructor of x is called automatically,
  // which does deallocation

答案 3 :(得分:1)

已经有很好的答案,但我认为你可能需要更多的澄清,所以我会尝试使这个更详细的答案,并尝试使其简单(如果我设法)。如果某些事情不明确(由于我不是母语为英语的人并且有时可能会出现制定答案的问题),请在评论中提问。还要使用Kerrek SB在答案中使用的变量 vs Objects 这个想法。

为了更清楚,我认为变量命名对象对象是在程序中存储数据的东西。

堆栈上的变量得到automatic storage duration,一旦它们的范围结束,它们就会自动被销毁并回收。

{
    std::string first_words = "Hello World!";

    // do some stuff here...

} // first_words goes out of scope and the memory gets reclaimed.

在这种情况下,first_words变量(因为它有自己的名称),这意味着它也是对象

现在堆怎么样?让我们将你可能认为是“堆上的东西”描述为变量指向堆上对象所在位置的某个内存位置。现在这些东西得到了所谓的dynamic storage duration

{
    std::string * dirty = nullptr

    {

        std::string * ohh = new std::string{"I don't like this"}    // ohh is a std::string* and a Variable
                                                                    // The actual std::string is only an unnamed
                                                                    // Object on the heap.

        // do something here

        dirty = ohh; // dirty points to the same memory location as ohh does now.

    }   // ohh goes out of scope and gets destroyed since it is a Variable.
        // The actual std::string Object on the heap doesn't get destroyed

    std::cout << *dirty << std::endl;   // Will work since the std::string on the heap that dirty points to
                                        // is still there.

    delete dirty; // now the object being pointed to gets destroyed and the memory reclaimed

    dirty = nullptr; can still access dirty since it's still in its scope.

} // dirty goes out of scope and get destroyed.

正如您所看到的,对象不符合范围,您必须手动管理其内存。这也是“大多数”人们更喜欢在其周围使用“包装”的原因。参见例如std :: string,它是动态“String”的包装器。

现在澄清你的一些问题:

  1. 为什么我们不能销毁堆栈中的对象?

    简单回答:你为什么要这样做?

    详细答案:它会被你摧毁,然后一旦它离开了不允许的范围就再次被销毁。此外,您通常应该只在您的范围内拥有您计算所需的变量,如果您确实需要该变量来完成计算,您将如何销毁它?但是如果你真的只需要在计算中花费一小段时间使用一个变量,你可以用{ }创建一个新的较小范围,这样你的变量就会在不再需要时自动销毁。

    注意:如果你有很多变量只需要计算的一小部分,那么可能暗示计算的那部分应该在它自己的函数/范围内。

  2. 从您的评论:是的,我得到了,但那是在变量范围的最后。它是否也在其范围的末尾释放堆上的变量?

    他们没有。堆上的对象没有范围,您可以将其地址从函数中传递出来并且它仍然存在。指向它的指针可能超出范围并被销毁,但堆上的Object仍然存在,您无法再访问它(内存泄漏)。这也是为什么它被称为手动内存管理,大多数人更喜欢它们周围的包装器,以便它不再需要时自动销毁。请参阅std :: string,std :: vector作为示例。

  3. 从你的评论中:你怎么能在电脑上耗尽内存?一个int占用4个字节,大多数计算机都有数十亿字节的内存......(不包括嵌入式系统)?

    好吧,计算机程序并不总是只持有一些int。让我回答一下“虚假”的引用:

      

    640K [计算机内存]对任何人都应该足够了。

    但这还不够,就像我们都应该知道的那样。多少内存就足够了?我不知道,但肯定不是我们现在得到的。有许多算法,问题和其他需要大量内存的东西。试想一下像电脑游戏这样的东西。如果我们有更多的记忆,我们可以制作“更大”的游戏吗?试想一下......你总是可以用更多的资源做出更大的东西,所以我认为没有任何限制我们可以说它已经足够了。

答案 4 :(得分:0)

读取函数调用 - 每次调用都会在堆栈上推送数据和函数地址。函数从堆栈中弹出数据并最终推送其结果。

通常,堆栈由操作系统管理,是的 - 它可以耗尽。试试这样的事情:

int main(int argc, char **argv)
     {
     int table[1000000000];
     return 0;
     }

这应该足够快结束。

答案 5 :(得分:0)

堆栈上的局部变量实际上不会被释放。指向当前堆栈的寄存器刚刚向上移动,堆栈“忘记”它们。是的,你可以占用很多堆栈空间,它会溢出并且程序崩溃 当程序退出时,操作系统会自动释放堆上的变量。如果你这样做

int x;
for(x=0; x<=99999999; x++) {
  int* a = malloc(sizeof(int));
}

a的值不断被覆盖,并且堆中存储的位置丢失。此内存未释放,因为程序不会退出。这称为“内存泄漏”。最终,你将耗尽堆上的所有内存,程序将崩溃。

答案 6 :(得分:0)

堆由代码管理:通过调用堆管理器来删除堆分配。堆栈由硬件管理。没有经理可以打电话。