VisualStudio 如何处理已删除的指针? 为什么?

我读过的一本 C + + 的书上说,当使用 delete操作符删除一个指针时,它所指向的位置的内存被“释放”,并且可以被覆盖。它还指出,指针将继续指向相同的位置,直到它被重新分配或设置为 NULL

但是在 VisualStudio2012中,情况似乎并非如此!

例如:

#include <iostream>


using namespace std;


int main()
{
int* ptr = new int;
cout << "ptr = " << ptr << endl;
delete ptr;
cout << "ptr = " << ptr << endl;


system("pause");


return 0;
}

当我编译并运行这个程序时,我得到以下输出:

ptr = 0050BC10
ptr = 00008123
Press any key to continue....

当调用 delete 时,指针指向的地址显然发生了变化!

为什么会发生这种情况? 这是否与 VisualStudio 有关?

如果 delete 可以改变它所指向的地址,那么为什么 delete 不会自动将指针设置为 NULL而不是随机地址呢?

9902 次浏览

我相信,您正在运行某种类型的调试模式,并且 VS 正在尝试将您的指针重新指向某个已知位置,以便进一步尝试取消引用它可以被跟踪和报告。尝试在发布模式下编译/运行相同的程序。

指针通常不会在 delete内部更改,这是为了提高效率,并避免给出错误的安全概念。在大多数复杂的场景中,将 delete 指针设置为预定义的值是没有用的,因为被删除的指针可能只是指向这个位置的几个指针中的一个。

事实上,我想得越多,就越觉得 VS 像往常一样犯了错误。如果指针是 const 怎么办?还会改变吗?

它还指出,指针将继续指向相同的位置,直到重新分配或设置为 NULL。

这绝对是误导性的信息。

当调用 delete 时,指针指向的地址显然发生了变化!

为什么会发生这种情况? 这是否与 VisualStudio 有关?

这显然符合语言规范。调用 delete之后,ptr无效。在经过 deleted 后才使用 ptr是引起未定义行为的原因。在调用 delete之后,运行时环境可以对 ptr做任何它想做的事情。

如果 delete 可以改变它所指向的地址,那么为什么 delete 不会自动将指针设置为 NULL,而是设置一些随机地址呢? ? ?

将指针的值更改为任何旧值都在语言规范中。至于将其改为 NULL,我会说,那将是糟糕的。如果将指针的值设置为 NULL,则程序的行为将更加正常。但是,这将隐藏问题。当程序使用不同的优化设置编译或移植到不同的环境时,问题很可能会在最不合时宜的时刻出现。

我注意到存储在 ptr中的地址总是被 00008123覆盖..。

这看起来很奇怪,所以我做了一些挖掘,发现这个 微软博客文章包含一个讨论“删除 C + + 对象时自动指针消毒”的部分。

... 检查 NULL 是一种常见的代码构造,这意味着现有的检查 NULL 结合使用 NULL 作为消毒值可以偶然地隐藏一个真正的内存安全问题,其根本原因确实需要解决。

出于这个原因,我们选择了0x8123作为一个消毒值——从操作系统的角度来看,它与零地址(NULL)在同一个内存页面中,但是0x8123的访问冲突会更好地引起开发人员的注意,因为他们需要更详细的注意。

它不仅解释了 Visual Studio 在指针被删除后如何处理它,还解释了为什么他们选择不将它自动设置为 NULL


这个“特性”是作为“ SDL 检查”设置的一部分启用的

为了证实这一点:

更改此设置并重新运行相同的代码将产生以下输出:

ptr = 007CBC10
ptr = 007CBC10

“ Feature”加引号是因为在有两个指向同一位置的指针的情况下,调用 delete 只会清除它们的 。另一个会指向无效的位置。


更新:

经过5年多的 C + + 编程经验,我意识到这整个问题基本上是一个没有实际意义的问题。如果你是一个 C + + 程序员,仍然使用 newdelete来管理原始指针,而不是使用智能指针(这绕过了整个问题) ,你可能会考虑改变职业道路,成为一个 C 程序员。;)

您将看到 /sdl编译选项的副作用。默认情况下为 VS2015项目打开,它支持除了/gs 提供的安全检查之外的其他安全检查。使用 Project > Properties > C/C + + > General > SDL 检查设置来更改它。

引自 MSDN 文章:

  • 执行有限的指针消毒。在不涉及解引用的表达式和没有用户定义析构函数的类型中,指针引用在调用删除后被设置为无效地址。这有助于防止旧指针引用的重用。

请记住,当您使用 MSVC 时,将已删除的指针设置为 NULL 是一种不好的做法。它击败了从 Debug Heap 和这个/sdl 选项获得的帮助,您不能再检测程序中无效的自由/删除调用。

delete ptr;
cout << "ptr = " << ptr << endl;

一般来说,无效指针的 阅读(就像您上面所做的那样,注意: 这与解引用不同)值(例如,当您 delete时,指针变得无效)是实现定义的行为。这是在 CWG # 1438中引入的。参见 给你

请注意,在此之前,读取无效指针的值是未定义的行为,所以上面的行为将是未定义的行为,这意味着任何事情都可能发生。

删除指针后,它所指向的内存可能仍然有效。若要显示此错误,请将指针值设置为明显的值。这确实有助于调试过程。如果将该值设置为 NULL,则它可能永远不会在程序流中显示为潜在的 bug。因此,当您稍后针对 NULL进行测试时,它可能会隐藏一个 bug。

另一点是,某些运行时优化器可能会检查该值并更改其结果。

在早些时候,MS 将值设置为 0xcfffffff