有问题的语言是C / C ++。
我的教授说当你完成使用它时释放堆上的内存,因为否则你最终会得到无法访问的内存。这样做的问题是你可能最终耗尽了所有内存,而且你无法访问它们。
为什么相同的概念不适用于堆栈?我知道你总是可以访问你在堆栈中使用的内存,但如果你不断创建新的变量,你最终会用完空间吗?那么为什么你不能在堆栈上释放变量来为堆上的新变量腾出空间呢?
我得到编译器释放堆栈上的变量,但那是在变量范围的末尾。它是否也在其范围的末尾释放堆上的变量?如果没有,为什么不呢?
答案 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”的包装器。
现在澄清你的一些问题:
为什么我们不能销毁堆栈中的对象?
简单回答:你为什么要这样做?
详细答案:它会被你摧毁,然后一旦它离开了不允许的范围就再次被销毁。此外,您通常应该只在您的范围内拥有您计算所需的变量,如果您确实需要该变量来完成计算,您将如何销毁它?但是如果你真的只需要在计算中花费一小段时间使用一个变量,你可以用{ }
创建一个新的较小范围,这样你的变量就会在不再需要时自动销毁。
注意:如果你有很多变量只需要计算的一小部分,那么可能暗示计算的那部分应该在它自己的函数/范围内。
从您的评论:是的,我得到了,但那是在变量范围的最后。它是否也在其范围的末尾释放堆上的变量?
他们没有。堆上的对象没有范围,您可以将其地址从函数中传递出来并且它仍然存在。指向它的指针可能超出范围并被销毁,但堆上的Object仍然存在,您无法再访问它(内存泄漏)。这也是为什么它被称为手动内存管理,大多数人更喜欢它们周围的包装器,以便它不再需要时自动销毁。请参阅std :: string,std :: vector作为示例。
从你的评论中:你怎么能在电脑上耗尽内存?一个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)
堆由代码管理:通过调用堆管理器来删除堆分配。堆栈由硬件管理。没有经理可以打电话。