我写了一个简单的,有效的俄罗斯方块游戏,每个块都是一个类单块的实例。
class SingleBlock
{
public:
SingleBlock(int, int);
~SingleBlock();
int x;
int y;
SingleBlock *next;
};
class MultiBlock
{
public:
MultiBlock(int, int);
SingleBlock *c, *d, *e, *f;
};
SingleBlock::SingleBlock(int a, int b)
{
x = a;
y = b;
}
SingleBlock::~SingleBlock()
{
x = 222;
}
MultiBlock::MultiBlock(int a, int b)
{
c = new SingleBlock (a,b);
d = c->next = new SingleBlock (a+10,b);
e = d->next = new SingleBlock (a+20,b);
f = e->next = new SingleBlock (a+30,b);
}
我有一个扫描完整行的函数,并运行删除相关行的块链接列表并重新分配 - >下一个指针。
SingleBlock *deleteBlock;
SingleBlock *tempBlock;
tempBlock = deleteBlock->next;
delete deleteBlock;
游戏正常运行,块被正确删除,一切都按照预期运行。但是在检查时我仍然可以访问已删除数据的随机位。
如果我在删除后删除了每个删除的单个块“x”值,其中一些会返回随机垃圾(确认删除),其中一些返回222,告诉我即使析构函数被调用,数据也不是实际上已从堆中删除。许多相同的试验表明,它总是与未正确删除的特定块相同。
结果:
Existing Blocks:
Block: 00E927A8
Block: 00E94290
Block: 00E942B0
Block: 00E942D0
Block: 00E942F0
Block: 00E94500
Block: 00E94520
Block: 00E94540
Block: 00E94560
Block: 00E945B0
Block: 00E945D0
Block: 00E945F0
Block: 00E94610
Block: 00E94660
Block: 00E94680
Block: 00E946A0
Deleting Blocks:
Deleting ... 00E942B0, X = 15288000
Deleting ... 00E942D0, X = 15286960
Deleting ... 00E94520, X = 15286992
Deleting ... 00E94540, X = 15270296
Deleting ... 00E94560, X = 222
Deleting ... 00E945D0, X = 15270296
Deleting ... 00E945F0, X = 222
Deleting ... 00E94610, X = 222
Deleting ... 00E94660, X = 15270296
Deleting ... 00E94680, X = 222
是否能够访问超出预期的数据?
对不起,如果这有点啰嗦。
答案 0 :(得分:77)
是否能够访问超出预期的数据?
这在技术上称为未定义行为。如果它能为你提供一罐啤酒,也不要感到惊讶。
答案 1 :(得分:31)
是否能够访问超出预期的数据?
在大多数情况下,是的。调用delete不会使内存归零。
请注意,未定义行为。使用某些编译器,可以将存储器归零。当您调用delete时,会发生内存被标记为可用,因此下次有人 new 时,可能会使用内存。
如果您考虑一下,这是合乎逻辑的 - 当您告诉编译器您不再对内存感兴趣时(使用 delete ),为什么计算机会花时间将其归零。
答案 2 :(得分:9)
这是C ++调用未定义的行为 - 您可能能够访问数据,您可能不会。无论如何,这是错误的。
答案 3 :(得分:8)
删除不会删除任何内容 - 它只是将内存标记为“可以重复使用”。在某些其他分配呼叫预留并填充该空间之前,它将具有旧数据。然而,依靠这是一个很大的禁忌,基本上如果你删除了忘记它的东西。
图书馆经常遇到的这方面的一个做法是删除功能:
template< class T > void Delete( T*& pointer )
{
delete pointer;
pointer = NULL;
}
这可以防止我们意外访问无效内存。
请注意,完全可以致电delete NULL;
。
答案 4 :(得分:3)
堆内存就像一堆黑板。想象一下你是一名教师。当你在教你的课时,黑板属于你,你可以做任何你想做的事情。你可以随意涂鸦并覆盖你想要的东西。
当课程结束并且您即将离开房间时,没有要求您擦除黑板的政策 - 您只需将黑板移交给下一位通常能够看到您所写内容的教师下来。
答案 5 :(得分:2)
通过delete()
释放内存时,系统不会清除内存。因此,在分配内存以供重用和覆盖之前,内容仍然可以访问。
答案 6 :(得分:1)
delete释放内存,但不修改或将其清零。你仍然不应该访问解除分配的内存。
答案 7 :(得分:1)
删除对象后,没有定义它占用的内存内容会发生什么。它确实意味着该内存可以自由重用,但实现不必覆盖最初的数据,也不必立即重用内存。
在对象消失后你不应该访问内存但不应该感到有些数据仍然存在。
答案 8 :(得分:0)
它不会零/改变记忆......但在某些时候,地毯将从你的脚下被拉下来。
不,它肯定不可预测:它取决于内存分配/释放的速度有多快。
答案 9 :(得分:0)
是的,有时可以预料到。 new
为数据保留空间,delete
只是使用new
创建的指针无效,允许在先前保留的位置写入数据;它不一定会删除数据。但是,您不应该依赖该行为,因为这些位置的数据可能随时发生变化,可能导致程序出现异常。这就是为什么在指针上使用delete
(或在delete[]
分配的数组上使用new[]
)之后,应该为它指定NULL,这样就不会篡改无效指针,假设在再次使用该指针之前不会使用new
或new[]
分配内存。
答案 10 :(得分:0)
它将导致未定义的行为并删除释放内存,它不会将其重新初始化为零。
如果你想把它归零,那就去做:
SingleBlock::~SingleBlock()
{ x = y = 0 ; }
答案 11 :(得分:0)
虽然运行时可能没有报告此错误,但使用正确的错误检查运行时(例如Valgrind)会在释放后提醒您使用内存。
我建议如果您使用new
/ delete
和原始指针(而不是std::make_shared()
和类似代码)编写代码,那么您在Valgrind下运行单元测试至少会有一个有机会发现这样的错误。
答案 12 :(得分:-1)
嗯,我一直想知道这件事已经有一段时间了,而且我试图进行一些测试,以便更好地了解幕后发生的事情。标准答案是,在您调用 delete 之后,您不应期望访问该内存点有任何好处。 但是,这对我来说似乎不够。调用 delete(ptr)时真正发生了什么?这是我发现的。我在Ubuntu 16.04上使用g ++,所以这可能会在结果中起作用。
使用delete运算符时我首先想到的是释放的内存将被传回系统以供其他进程使用。在我尝试的任何情况下,让我说这不会发生。
delete 发布的内存似乎仍然分配给它首先使用 new 分配的程序。我试过了,调用 delete 后没有内存使用量减少。我有一个软件通过新的调用大约30MB的列表,然后通过后续的删除调用释放它们。发生的事情是,在程序运行时查看系统监视器,甚至在删除调用后的长时间睡眠,内存消耗我的程序是相同的。没有减少!这意味着 delete 不会释放内存到系统。
事实上,看起来程序分配的内存是他永远的!但是,关键在于,如果解除分配,则可以由同一程序再次使用内存,而无需再分配。我尝试分配15MB,释放它们,然后分配另外15MB的数据,程序从未使用过30MB。系统监视器总是显示大约15MB。就上一次测试而言,我所做的只是改变事情发生的顺序:半分配,半分配,另一半分配。
所以,显然程序使用的内存可以增加,但永远不会缩小。我认为在紧急情况下可能会为其他进程释放内存,例如当没有更多可用内存时。毕竟,当其他进程要求时,让程序永远保留自己的内存会有什么意义呢?所以我再次分配了30MB,而同时解除了分配我运行了memtester
尽可能多的物理内存。我希望看到我的软件将其内存分发给memtester。但是猜猜看,它没有发生!
我制作了一个简短的截屏视频,展示了实际应用的内容:
诚实地说,有一种情况发生了某事。当我在程序的重新分配过程中尝试使用超过可用物理内存的memtester时,我的程序使用的内存减少到大约3MB。 memtester进程虽然被自动杀死,但发生的事情更令人惊讶!我的程序的内存使用量随着每次删除调用而增加!就好像Ubuntu在memtester事件发生后恢复了所有记忆一样。
取自 http://www.thecrowned.org/c-delete-operator-really-frees-memory