为什么不能在堆栈上释放变量?
Why can't you free variables on the stack?
有问题的语言是C/C++。
我的教授说,当你用完它时,要释放堆上的内存,否则你最终可能会得到无法访问的内存。这样做的问题是,您最终可能会用完所有内存,并且无法访问其中的任何内存。
为什么相同的概念不适用于堆栈?我知道您始终可以访问堆栈上使用的内存,但是如果您不断创建新变量,最终会耗尽空间,对吗?那么,为什么不能像在堆上那样释放堆栈上的变量,为新变量腾出空间呢?
我知道编译器释放了堆栈上的变量,但这是在变量权利范围的末尾。它不是也在其作用域结束时释放堆上的变量吗?如果没有,为什么不呢?
动态分配的对象(口语中的"堆对象")从来都不是变量。因此,它们永远不会超出范围。它们不存在于任何范围内。处理它们的唯一方法是通过分配时获得的指针。
(指针通常分配给变量,但这无济于事。
重复:变量具有范围;对象没有。但许多对象都是变量。
并回答这个问题:您只能释放对象,而不能释放变量。
闭合的"}"大括号的末尾是堆栈"释放"其内存的地方。所以如果我有:
{
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在它们之前被弹出。因此,每次编写"}"符号时,您都在有效地缩小堆栈并"释放"数据。
已经有很好的答案,但我认为您可能需要更多澄清,所以我会尝试使这个答案更详细,并尝试使其简单(如果我设法的话)。如果有些不清楚(因为我的母语不是英语,有时可能在制定答案时遇到问题),请在评论中询问。还将使用Kerrek SB在他的答案中使用的变量与对象的想法。
为了更清楚地说明这一点,我认为变量被命名为对象,对象是用于在程序中存储数据的东西。
堆栈上的变量得到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,它是动态"字符串"的包装器。
现在澄清您的一些问题:
为什么我们不能销毁堆栈上的对象?
简单的答案:你为什么要这样做?
详细答案:它会被你破坏,一旦它离开不允许的范围,就会再次被摧毁。此外,您通常应该只在范围内包含计算实际需要的变量,如果您确实需要该变量来完成计算,您将如何销毁它?但是,如果你真的只需要一个变量在计算中的一小段时间,你可以用
{ }
创建一个新的更小的作用域,这样你的变量就会在不再需要时自动销毁。注意:如果你有很多变量,你只需要计算的一小部分,这可能是一个暗示,这部分计算应该在它自己的函数/范围内。
从您的评论中:是的,我明白了,但这是在变量权利范围的末尾。它不是也在其作用域结束时释放堆上的变量吗?
他们没有。堆上的对象没有作用域,您可以将它们的地址从函数中传递出来,并且它仍然存在。指向它的指针可能会超出范围并被销毁,但堆上的对象仍然存在,您无法再访问它(内存泄漏)。这也是为什么它被称为手动内存管理,大多数人更喜欢包装器,以便在不再需要时自动销毁它。参见 std::string, std::vector 作为示例。
从您的评论: 另外,计算机上的内存如何耗尽?一个 int 占用大约 4 个字节,大多数计算机都有数十亿字节的内存......(不包括嵌入式系统)?
好吧,计算机程序并不总是只有几
int
。让我用一点"假"引用来回答:640K[计算机内存]对任何人来说都应该足够了。
但这还不够,我们都应该知道。多少内存才足够?我不知道,但肯定不是我们现在得到的。有许多算法,问题和其他东西需要大量内存。想想电脑游戏之类的东西。如果我们有更多的内存,我们能做出"更大"的游戏吗?想想就知道了...你总是可以用更多的资源做更大的事情,所以我认为没有任何限制,我们可以说这已经足够了。
那么,为什么不能像在堆上那样释放堆栈上的变量,为新变量腾出空间呢?
"堆栈分配器"知道的所有信息都是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
阅读有关函数调用的信息 - 每次调用都会在堆栈上推送数据和函数地址。函数从堆栈中弹出数据并最终推送其结果。
通常,堆栈由操作系统管理,是的 - 它可能会被耗尽。只需尝试执行以下操作:
int main(int argc, char **argv)
{
int table[1000000000];
return 0;
}
这种情况应该会很快结束。
堆栈上的局部变量实际上并没有被释放。指向当前堆栈的寄存器刚刚向上移动,堆栈"忘记"了它们。是的,您可以占用如此多的堆栈空间,以至于它溢出并导致程序崩溃。
堆上的变量确实会在程序退出时由操作系统自动释放。如果你这样做
int x;
for(x=0; x<=99999999; x++) {
int* a = malloc(sizeof(int));
}
A 的值不断被覆盖,堆中存储 A 的位置丢失。此内存未释放,因为程序不会退出。这称为"内存泄漏"。最终,您将耗尽堆上的所有内存,程序将崩溃。
堆由代码管理:删除堆分配是通过调用堆管理器来完成的。堆栈由硬件管理。没有经理可以打电话。
- 在将变量声明为引用时,堆在释放后使用
- 如果包含映射的静态库与可执行文件和动态库链接,静态映射(变量)是否会被多次释放?
- 在C++中创建和释放临时虚拟变量
- 如果一个变量在它之前释放了另一个(相同的数据类型)变量,如何将其分配给内存?
- 函数调用后释放变量
- 释放指向保留嵌套变量内存地址的结构的指针
- C++ 中指针变量的内存释放
- 如何释放为指针变量本身提供的内存?
- C 全局对象变量内存释放
- 如何释放C++异常类析构函数中的变量
- 如果我将内存动态分配给静态变量,我应该释放它还是会自动释放它
- 释放中间变量
- 如何释放局部变量拥有的资源
- 分配给 cpp 中定义的全局静态变量的内存是否在 C++ 中删除其类的实例后释放
- 为什么不能在堆栈上释放变量?
- 是否释放了局部变量的地址
- pthreads 中的条件变量并释放多个锁
- SysFreeString 不会释放变量
- 需要释放具有保留内存的 c++ Do 局部变量
- C++和Win32:我什么时候必须释放变量等