我们可以通过将连续的内存块转换为对象来创建对象。如果我们创建一个内存很少的对象,那么在访问它时它不会崩溃。但是在超出范围时它会崩溃。为什么呢?
class A
{
public:
int i;
};
void main(int argc, char * argv[])
{
{
vector<BYTE> v(1);
A* a = (A*)v.data();
a->i = 70000; // a's memory can't hold 4 bytes (int). Why no crash?
cout << a->i << endl; // Printed value - 70000. Why not crash here
} // Crashed here with heap corruption detected
cout << "End of scope\n";
}
答案 0 :(得分:3)
从形式上看,您只是在调用未定义的行为,因为您尝试将BYTE *
作为A *
进行访问。根据严格的别名规则,编译器就足以满足它想要的任何内容。
在现实世界中,它将取决于实施,并且可能会给出正确的结果。在幕后,vector
的常见实现使用为data
成员分配的内存。这意味着:
这意味着即使它是明确的未定义行为,这段代码也不会崩溃就不足为奇了。
注意:除了非常特殊的情况,并且红色闪烁字体的通知,您不应该依赖代码的实现细节,因为它可能会与下一版本的编译器崩溃,或者只是简单如果你改变构建选项。
堆损坏只是一个警告,因为你的实现在它给你的最后一个字节之后放了一个标记,并且发现该标记被覆盖了。但从低层次来看,你没有删除任何重要的东西 - 假设你的实现与我的一样: - )
答案 1 :(得分:1)
假设BYTE
是某种类型或typedef,sizeof(int) > sizeof(BYTE)
,而您的真实代码有一些忘记粘贴的#include
和using
指令,那么您的程序以多种方式导致未定义的行为:
int
的表达式用于写入BYTE
类型的存储,这违反了严格别名规则。 main
必须具有int
返回类型的规则。违反可诊断规则意味着生成的任何可执行文件都具有完全未定义的行为。当undefined behaviour发生时,任何事情都可能发生。这包括但不限于崩溃而不是崩溃。你不应该指望任何特定事件或缺乏特定事件。
答案 2 :(得分:1)
您正在使用的向量在堆上分配其内部缓冲区。
在1字节缓冲区上写入4个字节的缓冲区溢出,从而破坏堆。
当范围关闭时,向量的析构函数释放其内部缓冲区,检测到堆损坏。
答案 3 :(得分:0)
欢迎来到Undefined Behavior land。
a->i = 70000;
完全未定义=&gt;你在一个只有一个字节的东西里面写了int
。所以你不知道会发生什么:
只是猜测,我怀疑行a->i = 70000;
会产生一个缓冲区溢出,它会覆盖vector
的一些内部变量。因此在分配期间,由于已分配足够的内存,因此没有可见的故障。但是当vector
被销毁时,内部变量会产生不正确的行为,从而导致&#34;崩溃&#34;。
再一次,它只是猜测,因为它的未定义的行为。只有使用调试器,您才能观察到在崩溃之前发生了什么。
答案 4 :(得分:0)
您的程序不会崩溃。堆损坏是编译器添加的额外警告。
现在当对象被破坏时,它会检查内存是否仍然是&#34; OK&#34;。因为你将一个更大的对象放入内存中,所以超过了对象的内存被覆盖了。隐式运行时由编译器触发器进行检查,并给出此错误,告诉您内存存在问题&#34;。
当发生这种情况时,编译器依赖于内存检查的类型。你可以关闭内存检查,然后你不会在那里得到错误,但你仍然会损坏内存,并可能在以后有一些奇怪的(未定义的)行为。
此检查有多种方法。通常通过在分配的内存的末尾(有时也是前面)添加额外的特殊字节来打开&#34;删除&#34;检查它们是否仍在那里。