Visual Studio如何处理删除的指针,为什么?


130

我正在阅读的一本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;
}

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

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

显然,调用delete时指针指向的地址会更改!

为什么会这样呢?这与Visual Studio特别有关吗?

而且,如果delete仍然可以更改其指向的地址,那么为什么delete不会自动将指针设置NULL为某个随机地址呢?


4
删除指针,并不意味着将其设置为NULL,您必须注意这一点。
马特

11
我知道,但是我正在阅读的书特别指出,它仍将包含删除前所指向的地址,但是该地址的内容可能会被覆盖。
tjwrona1992

6
@ tjwrona1992,是的,因为这是通常发生的情况。该书仅列出最有可能的结果,而不是硬性规定。
SergeyA 2015年

5
@ tjwrona1992 我正在阅读的一本C ++书籍 -书籍的名称是...?
PaulMcKenzie 2015年

4
@ tjwrona1992:可能令人惊讶,但是无效指针值的所有使用都是未定义的行为,而不仅仅是取消引用。“检查指向的位置”是不允许使用该值的方式。
Ben Voigt,2015年

Answers:


175

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

这似乎很奇怪,所以我做了一些挖掘,发现这篇Microsoft博客文章包含一个讨论“删除C ++对象时自动清理指针”的部分。

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

因此,我们选择0x8123作为清除值-从操作系统的角度来看,它与零地址(NULL)位于同一内存页中,但是需要更详细地注意0x8123处的访问冲突会更好地吸引开发人员。

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


此“功能”是“ SDL检查”设置的一部分。要启用/禁用它,请转到:项目->属性->配置属性-> C / C ++->常规-> SDL检查

要确认这一点:

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

ptr = 007CBC10
ptr = 007CBC10

“功能”是在引号,因为在一个情况下,你有两个指针指向同一位置,要求删除只会消毒ONE他们。另一个将指向无效位置...


更新:

在5年以上的C ++编程经验之后,我意识到整个问题基本上都没有定论。如果您是C ++程序员,并且仍在使用newdelete管理原始指针,而不是使用智能指针(这避免了整个问题),您可能需要考虑改变职业道路以成为C程序员。;)


12
这是一个不错的发现。我希望MS可以更好地记录这种调试行为。例如,很高兴知道哪个编译器版本开始实现此功能以及哪些选项启用/禁用了行为。
迈克尔·伯

5
“从操作系统的角度来看,这和零地址在同一内存页中”-是吗?对于Windows和Linux,x86上的标准(忽略大页面)页面大小不是4kb吗?尽管我还记得雷蒙德·陈(Raymond Chen)博客上有关地址空间的前64kb的一些内容,所以在实践中我也得出了相同的结果,
Voo 2015年

12
@Voo窗口保留第一个(也是最后一个)64kB RAM作为陷阱的死空间。0x8123很好地掉入其中
棘轮怪胎

7
实际上,这不会鼓励不良习惯,也不允许您跳过将指针设置为NULL的原因,这就是他们使用0x8123而不是的全部原因0。指针仍然无效,但是在尝试取消引用时会导致异常(良好),并且它不会通过NULL检查(也很好,因为不这样做会出错)。坏习惯在哪里?这确实是可以帮助您调试的东西。
a安2015年

3
嗯,它不能同时设置两个(全部),所以这是第二好的选择。如果您不喜欢它,只需关闭SDL检查-我发现它们非常有用,尤其是在调试其他人的代码时。
a安2015年

30

您看到了副作用 /sdl compile选项。VS2015项目默认情况下处于启用状态,它启用了/ gs提供的安全检查以外的其他安全检查。使用项目>属性> C / C ++>常规> SDL检查设置来更改它。

引用MSDN文章

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

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


1
已确认。禁用此功能后,指针将不再重定向。感谢您提供修改它的实际设置!
tjwrona1992

汉斯,在您有两个指向同一位置的指针的情况下,将删除的指针设置为NULL是否仍然被认为是不好的做法?当您使用delete它时,Visual Studio将使第二个指针指向其原来的位置,该位置现在无效。
tjwrona1992

1
对我来说,还不清楚将指针设置为NULL会发生什么魔术。该其他指针不是这样,因此它无法解决任何问题,您仍然需要调试分配器来查找错误。
汉斯·帕桑

3
VS并没有清理指针。它破坏了他们。因此,无论如何使用它们,程序都会崩溃。调试分配器对堆内存执行相同的操作。NULL的大问题是,它没有足够的损坏。否则,通常的策略是google“ 0xdeadbeef”。
汉斯·帕桑

1
将指针设置为NULL仍然比将其指向现在无效的先前地址要好得多。尝试写入NULL指针不会破坏任何数据,并且可能会使程序崩溃。试图在那时重用指针甚至可能不会使程序崩溃,它可能只会产生非常不可预测的结果!
tjwrona1992

19

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

那绝对是误导性信息。

显然,调用delete时指针指向的地址会更改!

为什么会这样呢?这与Visual Studio特别有关吗?

这显然在语言规范之内。ptr呼叫后,无效delete。d ptr之后使用delete会导致未定义的行为。不要这样 ptr调用之后,运行时环境可以随意执行任何操作delete

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

将指针的值更改为任何旧值在语言规范之内。至于将其更改为NULL,那会很不好。如果将指针的值设置为NULL,则程序将以更合理的方式运行。但是,这将隐藏问题。当使用不同的优化设置编译程序或将程序移植到不同的环境时,该问题很可能会在最不适当的时候出现。


1
我认为它不能回答OP的问题。
SergeyA 2015年

即使在编辑后也不同意。将其设置为NULL不会隐藏问题-实际上,在没有这种情况的情况下,它会暴露在更多情况下。正常的实现没有这样做的原因是不同的。
SergeyA 2015年

4
@SergeyA,大多数实现都不是为了效率而这样做。但是,如果实现决定设置它,则最好将其设置为非NULL。与将其设置为NULL相比,它可以更快地揭示问题。它设置为NULL,delete在指针上调用两次不会引起问题。那绝对不好。
R Sahu

不,不是效率-至少,这不是主要问题。
SergeyA 2015年

7
@SergeyA将指针设置为非但NULL绝对不在进程的地址空间之外的值将比两种选择暴露更多的情况。如果将其悬空,则在释放后使用后,不一定会造成段错误。再次NULL将其设置为不会导致段错误delete
Blacklight Shining 2015年

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

通常,即使是读取(如您在上面所做的操作,也要注意:这与取消引用不同)都是无效的指针(例如,当您指针变为无效时delete)的值是实现定义的行为。这是在CWG#1438中引入的。另请参阅此处

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


3
同样引人注目的是[basic.stc.dynamic.deallocation]:“如果在标准库中提供给释放函数的参数是不是空指针值的指针,则释放函数应释放该指针引用的存储,从而使所有引用任何指针的指针均无效。部分的内容” [conv.lval](第4.1节)中的规则说,读取(左值->右值转换)任何无效的指针值都是实现定义的行为。
Ben Voigt

甚至UB可以由特定的供应商以特定的方式实现,从而至少对于该编译器而言是可靠的。如果Microsoft决定在CWG#1438之前实施其指针清理功能,那将不会使该功能或多或少可靠,尤其是如果启用了该功能,则“发生任何事情”根本不成立。 ,无论标准怎么说。
Kyle Strand

@KyleStrand:我基本上给出了UB的定义(blog.regehr.org/archives/213)。
giorgim

1
在SO上的大多数C ++社区中,“一切都会发生” 完全是字面上的。我认为这很荒谬。我了解UB的定义,但我也了解编译器只是由真正的人实现的软件,如果这些人实现了编译器,从而使其以某种方式运行,那么无论标准怎么说,编译器将如何运行
凯尔·斯特兰德

1

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

delete为了提高效率并避免给出错误的安全观念,通常不会在内部更改指针。在大多数复杂的情况下,将删除指针设置为预定义值将无济于事,因为要删除的指针可能只是指向该位置的若干指针之一。

事实上,我对它的思考越多,像往常一样,我越会发现VS在此方面存在错误。如果指针是const怎么办?它还会改变吗?


是的,即使常量指针也被重定向到了这个神秘的8123!
tjwrona1992

VS还有另外一块石头:)就在今天早上,有人问为什么他们应该使用g ++而不是VS。来了
SergeyA

7
@SergeyA,但是从另一端推迟删除指针将通过segfault向您显示您试图取消引用已删除指针,并且它不等于NULL。在另一种情况下,它只会在页面也被释放时才崩溃(这是极不可能的)。失败更快;早点解决。
棘轮怪胎

1
@ratchetfreak“快速失败,更快解决”是一个非常有价值的口头禅,但是“通过破坏关键的法证证据来快速失败”并没有那么有价值。在简单的情况下,这可能很方便,但是在更复杂的情况下(我们通常需要最大帮助的情况),擦除有价值的信息会减少我用于解决问题的工具。
Cort Ammon 2015年

2
@ tjwrona1992:我认为微软在这里做对了。消毒一个指针总比不执行任何操作要好。如果这在调试中造成问题,请在错误的delete调用之前放置一个断点。奇怪的是,没有这样的东西,您将永远无法发现问题。并且,如果您有更好的解决方案来定位这些错误,请使用它,为什么您要关心Microsoft的工作?
Zan Lynx 2015年

0

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

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

在早期,MS将值设置为0xcfffffff

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.