我这里有一个非常简单的 C++ 代码示例:
char *s = new char[100];
strcpy(s, "HELLO");
delete [] s;
int n = strlen(s);
如果我通过按 F5(开始调试)从 Visual C++ 2008 运行此代码,这总是会导致崩溃(访问冲突)。但是,在外部启动此可执行文件IDE 或使用 IDE 的 Ctrl+F5(启动但不调试)不会导致任何崩溃。有什么区别?
我还想知道是否可以稳定地重现访问已删除区域导致的访问冲突崩溃?这种车祸在现实生活中很少见吗?
I have a very simple C++ code sample here:
char *s = new char[100];
strcpy(s, "HELLO");
delete [] s;
int n = strlen(s);
If I run this code from Visual C++ 2008 by pressing F5 (Start Debugging,) this always result in crash (Access Violation.) However, starting this executable outside the IDE, or using the IDE's Ctrl+F5 (Start without Debugging) doesn't result in any crash. What could be the difference?
I also want to know if it's possible to stably reproduce the Access Violation crash caused from accessing deleted area? Is this kind of crash rare in real-life?
发布评论
评论(6)
通过已删除的指针访问内存是未定义的行为。您不能期望任何可靠/可重复的行为。
最有可能的是它在一种情况下“有效”,因为字符串仍然“坐在那里”在现在可用的内存中 -= 但你不能依赖它。 VS 用调试值填充内存,以帮助强制崩溃,从而帮助找到这些错误。
Accessing memory through a deleted pointer is undefined behavior. You can't expect any reliable/repeatable behavior.
Most likely it "works" in the one case because the string is still "sitting there" in the now available memory -= but you cannot rely on that. VS fills memory with debug values to help force crashes to help find these errors.
不同之处在于,调试器、调试库以及在“调试”模式下构建的代码喜欢破坏应该破坏的东西。您的代码应该会中断(因为它访问技术上不再拥有的内存),因此在编译进行调试并在调试器中运行时更容易中断。
在现实生活中,您通常不会收到如此明显的通知。所有这些东西都会使事情在调试器中应该出现的情况下崩溃……这些东西很昂贵。所以在发布时并没有严格检查。您可能 100 次中有 99 次能够释放一些内存并在之后立即访问它,因为运行时库并不总是立即将内存交还给操作系统。但是第 100 次,要么内存消失了,要么另一个线程现在拥有它,你得到的字符串长度不再是字符串,而是一个 252462649 字节的废话数组,它一头扎进未分配的内存(因此非分配内存)。存在的,就你或运行时应该关心的而言)内存。几乎没有什么可以告诉你刚刚发生了什么。
所以不要这样做。一旦你删除了某些东西,就认为它已经死了。否则你会浪费半生去寻找海森虫。
The difference is that a debugger, and debug libraries, and code built in "debug" mode, likes to break stuff that should break. Your code should break (because it accesses memory it no longer technically owns), so it breaks easier when compiled for debugging and run in the debugger.
In real life, you don't generally get such unsubtle notice. All that stuff that makes things break when they should in the debugger...that stuff's expensive. So it's not checked as strictly in release. You might be able 99 times out of 100 to get away with freeing some memory and accessing it right after, cause the runtime libs don't always hand the memory back to the OS right away. But that 100th time, either the memory's gone, or another thread owns it now and you're getting the length of a string that's no longer a string, but a 252462649-byte array of crap that runs headlong into unallocated (and thus non-existent, as far as you or the runtime should care) memory. And there's next to nothing to tell you what just happened.
So don't do that. Once you've deleted something, consider it dead and gone. Or you'll be wasting half your life tracking down heisenbugs.
在删除后取消引用指针是未定义的行为 - 任何事情都可能发生,包括但不限于:
确切的结果将取决于多个因素,其中大多数因素是您无法控制的。如果一开始就不要触发未定义的行为,你会更好。
Dereferencing a pointer after
delete
is undefined behavior - anything can happen, including but not limited to:exact results will depend on multiple factors most of which are out of your control. You'll be much better off not triggering undefined behavior in the first place.
通常,从进程的角度来看,分配和释放的内存没有区别。例如,该进程只有一张按需增长的大内存映射。
访问冲突是由于读取/写入不可用的内存(通常未分页到进程中)引起的。各种运行时内存调试实用程序使用分页机制来跟踪无效内存访问,而不会像软件内存检查那样造成严重的运行时损失。
无论如何,您的示例仅证明在一种环境中运行程序时有时会检测到错误,但在另一种环境中未检测到错误,但它仍然是一个错误,并且上面代码的行为未定义。
Usually, there is no difference in allocated and freed memory from a process perspective. E.g the process only has one large memory map that grows on demand.
Access violation is caused by reading/writing memory that is not available, ususally not paged in to the process. Various run-time memory debugging utilities uses the paging mechanism to track invalid memory accesses without the severe run time penalty that software memory checking would have.
Anyway your example proves only that an error is sometimes detected when running the program in one environment, but not detected in another environment, but it is still an error and the behaviour of the code above is undefined.
带有调试符号的可执行文件能够检测某些访问冲突的情况。检测此问题的代码包含在可执行文件中,但默认情况下不会触发。
您可以在此处找到有关如何控制调试器外部行为的说明:http://msdn.microsoft.com/en-us/library/w500y392%28v=VS.80%29.aspx
The executable with debug symbols is able to detect some cases of access violations. The code to detect this is contained in the executable, but will not be triggered by default.
Here you'll find an explanation of how you can control behaviour outside of a debugger: http://msdn.microsoft.com/en-us/library/w500y392%28v=VS.80%29.aspx
您可以考虑使用内联函数,而不是简单的删除,该函数还将已删除指针的值设置为 0/NULL。如果您引用它,通常会崩溃。但是,如果您第二次删除它,它不会抱怨。
不,这种崩溃可能是您和我在软件中看到的大多数崩溃的原因。
Instead of plain
delete
you could consider using an inline function that also sets the value of the deleted pointer to 0/NULL. This will typically crash if you reference it. However, it won't complain if you delete it a second time.No, this kind of crash is probably behind the majority of the crashes you and I see in software.