Visual Studio对删除的指针做了什么?为什么?

tjw*_*992 127 c++ pointers memory-management delete-operator visual-studio-2012

我一直在阅读的C++书中指出,当使用delete操作符删除指针时,指向它的位置的内存被"释放"并且可以被覆盖.它还指出指针将继续指向同一位置,直到重新分配或设置为NULL.

但是在Visual Studio 2012中; 这似乎不是这样的!

例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

ptr = 0050BC10
ptr = 00008123
Press any key to continue....
Run Code Online (Sandbox Code Playgroud)

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

为什么会这样?这是否与Visual Studio有关?

如果删除可以改变它指向的地址,为什么不删除自动设置指针NULL而不是一些随机地址?

tjw*_*992 173

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

这看起来很奇怪,所以我做了一点挖掘,发现这篇Microsoft博客文章包含了一节讨论"删除C++对象时自动指针清理".

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

出于这个原因,我们选择了0x8123作为清理值 - 从操作系统的角度来看,这与零地址(NULL)在同一个内存页面中,但是在0x8123处的访问冲突将更好地脱颖而出,因为需要更详细的关注.

它不仅解释了Visual Studio在删除后对指针的作用,还解释了为什么他们选择不NULL自动设置它!


此"功能"作为"SDL检查"设置的一部分启用.要启用/禁用它,请转到:PROJECT - > Properties - > Configuration Properties - > C/C++ - > General - > SDL check

要确认这一点:

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

ptr = 007CBC10
ptr = 007CBC10
Run Code Online (Sandbox Code Playgroud)

"功能"是因为在你有两个指针指向同一位置的情况下报价,呼吁删除只会消毒ONE他们.另一个将指向无效位置.

Visual Studio可以通过在设计中记录这个缺陷来为您设置一个棘手的情况.

  • @Voo windows保留第一个(和最后一个)64kB的RAM作为陷阱的死区.0x8123非常适合 (12认同)
  • 这是一个很好的发现.我希望MS能更好地记录这样的调试行为.例如,很高兴知道哪个编译器版本开始实现这个以及哪些选项启用/禁用该行为. (11认同)
  • 实际上,它不鼓励坏习惯,并且它不允许你跳过将指针设置为NULL - 这就是他们使用`0x8123`而不是'0`的全部原因.指针仍然无效,但在尝试取消引用它时会导致异常(好),并且它*不会传递NULL检查(也很好,因为不这样做是错误的).坏习惯的地方在哪里?它确实只是帮助您调试的东西. (6认同)
  • "从操作系统的角度来看,这与零地址在同一个内存页面中" - 是吗?对于Windows和Linux,x86上的标准(忽略大页面)页面大小是不是4kb?虽然我在Raymond Chen的博客上模糊地记住了第一个64kb的地址空间,但实际上我也采用了相同的结果, (5认同)
  • 好吧,它不能同时设置它们(全部),所以这是第二个最佳选择.如果您不喜欢它,只需关闭SDL检查 - 我发现它们非常有用,尤其是在调试其他人的代码时. (3认同)

Han*_*ant 30

您会看到/sdl编译选项的副作用.默认情况下,对于VS2015项目,它启用了除/ gs提供的安全检查之外的其他安全检查.使用项目>属性> C/C++>常规> SDL检查设置来更改它.

引用MSDN文章:

  • 执行有限的指针清理.在不涉及解除引用的表达式中以及没有用户定义的析构函数的类型中,在调用delete之后,指针引用被设置为无效地址.这有助于防止重用过时的指针引用.

请记住,在使用MSVC时,将删除的指针设置为NULL是一种不好的做法.它会破坏您从Debug Heap和this/sdl选项获得的帮助,您无法再在程序中检测到无效的免费/删除调用.

  • VS确实*不*清理指针.它腐蚀了他们.所以当你使用它们时你的程序会崩溃.调试分配器对堆内存做了很多相同的事情.NULL的大问题,它不够腐败.否则一个共同的策略,谷歌"0xdeadbeef". (3认同)

R S*_*ahu 19

它还指出指针将继续指向同一位置,直到它被重新分配或设置为NULL.

这绝对是误导性的信息.

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

为什么会这样?这是否与Visual Studio有关?

这显然属于语言规范.ptr呼叫后无效delete.ptr在它之后使用delete是造成未定义行为的原因.不要这样做.运行时环境可以ptr在调用后随意执行任何操作delete.

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

将指针的值更改为任何旧值都在语言规范内.至于把它改成NULL,我会说,那会很糟糕.如果指针的值设置为NULL,程序将以更合理的方式运行.但是,这将隐藏问题.当程序使用不同的优化设置进行编译或移植到不同的环境时,问题可能会出现在最不合时宜的时刻.

  • @SergeyA设置一个指向一个不是"NULL"但是肯定在进程'地址空间之外的值的指针将暴露出比两个替代方案更多的情况.如果在被释放后使用它,那么悬挂它不一定会导致段错误; 将其设置为"NULL"如果再次"删除",则不会导致段错误. (7认同)
  • @SergeyA,大多数实现都不是为了提高效率.但是,如果实现决定设置它,最好将其设置为非NULL.它会比将它设置为NULL更快地揭示问题.它设置为NULL,在指针上调用两次"delete"不会导致问题.这绝对不是好事. (4认同)

gio*_*gim 10

delete ptr;
cout << "ptr = " << ptr << endl;
Run Code Online (Sandbox Code Playgroud)

一般情况下,即使读取(如上所述,请注意:这与解除引用不同)无效指针的值(指针变为无效,例如当你使用delete它时)是实现定义的行为.这是在CWG#1438中引入的.另见这里.

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

  • 同样相关的是来自`[basic.stc.dynamic.deallocation]`的引用:"如果标准库中给予释放函数的参数是一个不是空指针值的指针,则释放函数将释放引用的存储空间通过指针,渲染无效的所有指向解除分配存储的任何部分的指针"和`[conv.lval]`(4.1节)中的规则,即读取(左值 - >右值转换)任何无效指针值是实现定义的行为. (3认同)