所以要更好地理解new / delete(真的要用自己的小例子来证明为什么接口需要虚拟析构函数),我想了解内存泄漏,这样我就可能生活在对它们的恐惧之中。但是我很难接触到泄漏,可以这么说;实际上,我也很难用new / delete。
这是我最简单的版本:
int* P1 = new int(43);
cout<<"P1 = "<<P1<<endl;
cout<<"*P1 = "<<*P1<<endl;
delete P1;
cout<<"P1 = "<<P1<<endl;
cout<<"*P1 = "<<*P1<<endl;
打印:
P1 = 0xcc0340
*P1 = 43
P1 = 0xcc0340
*P1 = 43
我在课堂上有一些更复杂的东西,但是这个例子说明了我的失败。我认为删除需要一个指针并释放它的内存,从而使指针无效或至少它指向什么?我必须做一些非常简单的事情。
答案 0 :(得分:25)
您正在导致未定义的行为。这意味着任何事情都可能发生。由于确实发生了某些事情,所有事情都表现得有记录。 (有时候“某些东西”看起来非常类似于你可能错过的其他东西。完全按照你认为你想要实现的目标是“未定义行为”可能允许的实例之一。)
另请注意,“内存泄漏”与您尝试执行的操作相反 - 在内存泄漏中您忘记释放内存,而您已经释放内存并且现在正在访问无效内存。
这是一个真正的内存泄漏,它也不导致未定义的行为 - 不要混淆“糟糕但正确”与“不正确”的编程!
int * factorial(int * n)
{
if (*n == 0) return new int(1);
else return new int(*n * *factorial(*n - 1));
}
答案 1 :(得分:23)
我必须做一些非常简单的事情。
你是对的。你的代码正在做一些非常简单的非常错误的事情,并且你付出了最终的代价 - 没有看到任何负面结果。有时,你做错了事,没有什么不好的事情发生。这是最糟糕的结果,因为您可能没有意识到您的代码是错误的,并且您将继续使用它,直到有一天您的代码意外中断,并且您将不知道在哪里或如何,因为它始终有效。它会破坏与错误的实际部分无关的某个地方,你会花费数小时试图追踪它并找出它被破坏的原因。
当你做错事时,这是未定义的行为。这意味着一切都会发生。充其量,你的代码崩溃,段错误和闪烁的灯亮起来,说“你正在使用自由记忆”,一切都很清楚。最糟糕的是,demons fly out of your nose。但正好在这之上,第二个可能的结果就是一切看起来像你想要的那样有效。
答案 2 :(得分:5)
这将是内存泄漏:
int* P1 = new int(43);
P1 = new int(42);
分配内存而不再删除它。
答案 3 :(得分:4)
// Reserve some memory for an int and set that memory to the value 43.
int* P1 = new int(43);
// Print the address of the reserved memory.
cout<<"P1 = "<<P1<<endl;
// Print the contents of that memory.
cout<<"*P1 = "<<*P1<<endl;
// Free the memory - it is no longer reserved to you.
delete P1;
// int* P2 = new int(47);
// Print the address of the memory. It still holds the address to
// the memory that used to be reserved for you.
cout<<"P1 = "<<P1<<endl;
// Print the current value of the memory that used to be reserved.
cout<<"*P1 = "<<*P1<<endl;
如果您取消注释P2行,很可能会为它分配相同的内存,这会改变最后一行打印的值。
正如其他人所指出的,访问已使用delete
释放的内存会导致未定义的行为。未定义包括在某些情况下以奇怪的方式崩溃(仅在满月期间?;-)。它还包括现在完美运行的一切,但是当你在程序中的任何其他位置进行另一次更改时,该错误就会成为一个漏洞。
内存韭菜是指您使用new
分配内存并且永远不会使用delete
释放内存。在有人运行你的程序较长时间并发现它占用了系统的所有内存之前,通常不会注意到这一点。
答案 4 :(得分:3)
此代码
delete P1;
cout<<"P1 = "<<P1<<endl;
cout<<"*P1 = "<<*P1<<endl;
导致未定义的行为。所以一切都会发生。导致内存泄漏要容易得多:
for(;;) new int;
答案 5 :(得分:2)
取消引用已删除的指针是未定义的行为,如前所述,即您做错了什么。编译器可以帮助通过将指针的值更改为某些 magic 值来找到该错误,该值将始终导致取消引用指针失败并且可以为您提供线索,因为它是以前删除过。
快速测试显示以下行为:
MSVC2010:
debug build:* P1 = 0xfeeefeee
发布版本:* P1 =&lt; random&gt;
GCC 4.6:
调试&amp;发布版本:* P1 = 0
所以你只是(不)幸运,你再次获得了原始价值。我建议你始终建立调试模式来测试程序(即使你没有调试),因为它会为你的代码增加额外的健全性检查。
答案 6 :(得分:1)
内存已释放但未清除。该值可能会保留在内存中,直到某个其他进程在该位置写入新值。
答案 7 :(得分:0)
删除不会使指针无效。它释放它指向堆的内存。在这种情况下,内存尚未重用,因此仍包含相同的内容。在某些时候,内存将被重用,然后你得到其他人所说的未定义的行为。
但这不是内存泄漏。当你指向另一个内存分配(或者只是丢弃指针)而不释放它指向的内存时。第一个然后保持分配,因为你没有引用它,你无法释放它。