取消引用已删除的指针总是会导致访问冲突?
Posted
技术标签:
【中文标题】取消引用已删除的指针总是会导致访问冲突?【英文标题】:Dereferencing deleted pointers always result in an Access Violation? 【发布时间】:2011-02-23 04:27:02 【问题描述】:我这里有一个非常简单的 C++ 代码:
char *s = new char[100];
strcpy(s, "HELLO");
delete [] s;
int n = strlen(s);
如果我通过按 F5(开始调试)从 Visual C++ 2008 运行此代码,这总是会导致崩溃(访问冲突。)但是,在 IDE 外部启动此可执行文件,或使用 IDE 的Ctrl+F5(不调试就开始)不会导致任何崩溃。有什么区别?
我也想知道是否可以稳定重现访问删除区域导致的访问冲突崩溃?这种崩溃在现实生活中是不是很少见?
【问题讨论】:
它会产生未定义的行为,这意味着程序可以为所欲为。不过,它不会在调试器之外崩溃,这有点奇怪。实际发生了什么?如果这是一个控制台程序,输出是什么? 处理已删除的指针确实很痛苦。这就是为什么您应该使用std::vector
或在这种特殊情况下使用std::string
而不是new[]
。您永远不必使用delete
。
代码示例是从实际应用程序中截取的。我正在重现一个很少发生的错误,可能只有当一个线程执行delete
而另一个线程取消引用时。因此我想要一种方法来稳定地重现它。 SO并没有让我失望所有这些有用的答案:)
【参考方案1】:
通过已删除的指针访问内存是未定义的行为。您不能指望任何可靠/可重复的行为。
它很可能在一种情况下“有效”,因为字符串仍然“坐在那里”在现在可用的内存中 -= 但你不能依赖它。 VS 使用调试值填充内存以帮助强制崩溃以帮助查找这些错误。
【讨论】:
【参考方案2】:不同之处在于调试器、调试库以及在“调试”模式下构建的代码喜欢破坏应该破坏的东西。您的代码应该会中断(因为它会访问技术上不再拥有的内存),因此在为调试而编译并在调试器中运行时,它会更容易中断。
在现实生活中,您通常不会得到如此隐晦的通知。所有那些在调试器中应该让事情崩溃的东西……这些东西很昂贵。所以它没有在发布时严格检查。您可能 100 次中有 99 次可以释放一些内存并在之后立即访问它,因为运行时库并不总是立即将内存交还给操作系统。但是第 100 次,要么内存消失了,要么另一个线程现在拥有它,你得到的字符串长度不再是一个字符串,而是一个 252462649 字节的废话数组,它一头扎进未分配的(因此非存在,只要你或运行时应该关心)内存。几乎没有什么可以告诉你刚刚发生了什么。
所以不要那样做。一旦你删除了一些东西,就认为它已经死了。否则你将浪费大半辈子的时间来追踪海森虫。
【讨论】:
很好地参考了 heisenbugs :D【参考方案3】:在delete
之后取消引用指针是未定义的行为 - 任何事情都可能发生,包括但不限于:
确切的结果取决于多种因素,其中大部分是您无法控制的。最好不要一开始就触发未定义的行为。
【讨论】:
【参考方案4】:通常,从进程的角度来看,分配和释放的内存没有区别。例如,该进程只有一个按需增长的大型内存映射。
访问冲突是由不可用的读/写内存引起的,通常没有分页到进程中。各种运行时内存调试实用程序使用分页机制来跟踪无效的内存访问,而不会像软件内存检查那样造成严重的运行时损失。
无论如何你的例子只是证明了在一个环境中运行程序时有时会检测到错误,但在另一个环境中没有检测到,但它仍然是一个错误并且上面代码的行为是未定义的。
【讨论】:
【参考方案5】:带有调试符号的可执行文件能够检测到某些访问违规的情况。检测这一点的代码包含在可执行文件中,但默认情况下不会触发。
您可以在此处找到有关如何控制调试器之外的行为的说明:http://msdn.microsoft.com/en-us/library/w500y392%28v=VS.80%29.aspx
【讨论】:
【参考方案6】:我也想知道有没有可能 稳定再现Access 访问导致的违规崩溃 删除的区域?
您可以考虑使用内联函数来代替普通的delete
,该函数还将已删除指针的值设置为0/NULL。如果您引用它,这通常会崩溃。但是,如果您再次删除它,它不会抱怨。
这种崩溃在国内很少见吗 现实生活?
不,这种崩溃可能是您和我在软件中看到的大多数崩溃的原因。
【讨论】:
以上是关于取消引用已删除的指针总是会导致访问冲突?的主要内容,如果未能解决你的问题,请参考以下文章