41

我编写了一个简单的、有效的俄罗斯方块游戏,每个块都作为类单块的实例。

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);
}

我有一个扫描完整行的函数,并遍历块的链接列表,删除相关的块并重新分配 ->next 指针。

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

是否能够从超出预期的坟墓中访问数据?

对不起,如果这有点啰嗦。

4

13 回答 13

95

是否能够从超出预期的坟墓中访问数据?

这在技术上称为未定义行为。如果它为您提供一罐啤酒,请不要感到惊讶。

于 2009-12-18T20:22:29.043 回答
42

是否能够从超出预期的坟墓中访问数据?

在大多数情况下,是的。调用 delete 不会将内存归零。

请注意,行为未定义。使用某些编译器,内存可能会被清零。当你调用 delete 时,内存被标记为可用,所以下次有人做new时,内存可能会被使用。

如果您考虑一下,这是合乎逻辑的-当您告诉编译器您不再对内存感兴趣时(使用delete),计算机为什么要花时间将其归零。

于 2009-12-18T20:22:24.753 回答
18

删除不会删除任何东西——它只是将内存标记为“可以重用”。在其他一些分配调用保留并填充该空间之前,它将拥有旧数据。但是,依赖它是一个很大的禁忌,基本上如果你删除一些东西就忘了它。

在库中经常遇到的这方面的一种做法是删除函数:

template< class T > void Delete( T*& pointer )
{
    delete pointer;
    pointer = NULL;
}

这可以防止我们意外访问无效内存。

请注意,调用delete NULL;.

于 2009-12-18T20:32:06.420 回答
10

这就是 C++ 所称的未定义行为——您可能能够访问数据,但可能不能。无论如何,这是错误的做法。

于 2009-12-18T20:22:33.443 回答
6

堆内存就像一堆黑板。想象你是一名老师。当你在教你的课时,黑板是属于你的,你可以用它做任何你想做的事情。您可以随意涂鸦并覆盖内容。

当课程结束并且您即将离开房间时,没有规定要求您擦除黑板 - 您只需将黑板交给下一位老师,他们通常可以看到您写下的内容。

于 2009-12-18T21:12:14.590 回答
3

通过 释放内存时系统不会清除内存delete()。因此,在分配内存以供重用和覆盖之前,这些内容仍然可以访问。

于 2009-12-18T20:22:57.110 回答
3

删除一个对象后,它没有定义它占用的内存内容会发生什么。这确实意味着该内存可以自由重用,但实现不必覆盖最初存在的数据,也不必立即重用内存。

你不应该在对象消失后访问内存,但不应该感到惊讶的是,一些数据仍然完好无损。

于 2009-12-18T20:23:27.527 回答
1

delete 释放内存,但不修改它或将其清零。您仍然不应该访问已释放的内存。

于 2009-12-18T20:22:27.810 回答
1

是的,有时可以预期。为数据new保留空间,delete只是使用创建的指针无效new,从而允许将数据写入先前保留的位置;它不一定会删除数据。但是,您不应该依赖这种行为,因为这些位置的数据可能随时更改,可能导致您的程序行为不端。这就是为什么在使用delete指针(或delete[]用 分配的数组new[])之后,您应该为其分配 NULL 以便您不能篡改无效指针,假设您不会使用newnew[]在再次使用该指针之前分配内存.

于 2009-12-18T20:25:54.450 回答
0

它还不会归零/改变记忆......但在某些时候,地毯会从你的脚下拉出来。

不,这肯定是不可预测的:这取决于内存分配/释放的速度。

于 2009-12-18T20:22:20.730 回答
0

尽管您的运行时可能不会报告此错误,但使用适当的错误检查运行时(例如 Valgrind)会在内存释放后提醒您内存的使用情况。

我建议如果您使用new/delete和原始指针(而不是std::make_shared()类似的)编写代码,那么您在 Valgrind 下进行单元测试至少有机会发现此类错误。

于 2016-08-31T10:03:30.667 回答
-1

它将导致未定义的行为并且 delete 释放内存,它不会用零重新初始化它。

如果您想将其归零,请执行以下操作:

SingleBlock::~SingleBlock()

{    x = y = 0 ; }
于 2009-12-18T20:39:22.783 回答
-3

好吧,我也一直在想这个问题,并且我尝试运行一些测试以更好地了解引擎盖下发生了什么。标准答案是,在您调用delete之后,您不应该期望访问该内存点有什么好处。然而,这对我来说似乎还不够。调用delete(ptr)时到底发生了什么?这是我发现的。我在 Ubuntu 16.04 上使用 g++,所以这可能会对结果产生影响。

在使用 delete 操作符时,我首先期望的是释放的内存将被交还给系统以供其他进程使用。让我说这在我尝试过的任何情况下都不会发生。

用delete释放的内存似乎仍然分配给它首先用new分配的程序。我试过了,调用delete后内存使用量没有减少。我有一个软件,它通过新调用分配了大约 30MB 的列表,然后在随后的删除调用中释放它们。发生的事情是,在程序运行时查看系统监视器,即使在删除调用后长时间休眠,我的程序的内存消耗也是一样的。没有减少!这意味着删除不会向系统释放内存。

事实上,看起来程序分配的内存是他永远的!然而,关键是,如果被释放,内存可以被同一个程序再次使用,而不必再分配。我尝试分配 15MB,释放它们,然后再分配 15MB 的数据,程序从未使用过 30MB。系统监视器总是显示它大约 15MB。对于之前的测试,我所做的只是改变了事情发生的顺序:一半分配,一半释放,另一半分配。

因此,显然程序使用的内存可以增加,但永远不会缩小。我认为在紧急情况下,例如当没有更多可用内存时,可能真的会为其他进程释放内存。毕竟,当其他进程要求它时,让一个程序永远保留它自己的内存有什么意义呢?所以我再次分配了 30MB,在释放它们的同时,我运行了memtester尽可能多的物理内存。我希望看到我的软件将其内存分配给 memtester。但你猜,它并没有发生!

我制作了一个简短的截屏视频,展示了实际情况:

删除示例记忆

说实话,有一种情况发生了一些事情。当我在程序的释放过程中尝试使用超过可用物理内存的 memtester 时,我的程序使用的内存下降到大约 3MB。memtester 进程虽然被自动杀死了,但发生的事情更令人惊讶!每次删除调用都会增加我的程序的内存使用量!就好像 Ubuntu 在 memtester 事件之后恢复了所有的内存。

取自 http://www.thecrowned.org/c-delete-operator-really-frees-memory

于 2017-03-06T15:29:51.033 回答