如何在投射时分配内存?

时间:2016-08-31 09:28:28

标签: c++

我们可以通过将连续的内存块转换为对象来创建对象。如果我们创建一个内存很少的对象,那么在访问它时它不会崩溃。但是在超出范围时它会崩溃。为什么呢?

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";
}

5 个答案:

答案 0 :(得分:3)

从形式上看,您只是在调用未定义的行为,因为您尝试将BYTE *作为A *进行访问。根据严格的别名规则,编译器就足以满足它想要的任何内容。

在现实世界中,它将取决于实施,并且可能会给出正确的结果。在幕后,vector的常见实现使用为data成员分配的内存。这意味着:

  • 它将正确对齐任何类型
  • 对于int来说,实际分配的内存可能至少足够(因为对齐,32位系统至少有4个字节,64位系统有8个字节)
  • 您可以安全地更改已分配内存的类型

这意味着即使它是明确的未定义行为,这段代码也不会崩溃就不足为奇了。

注意:除了非常特殊的情况,并且红色闪烁字体的通知,您不应该依赖代码的实现细节,因为它可能会与下一版本的编译器崩溃,或者只是简单如果你改变构建选项。

堆损坏只是一个警告,因为你的实现在它给你的最后一个字节之后放了一个标记,并且发现该标记被覆盖了。但从低层次来看,你没有删除任何重要的东西 - 假设你的实现与我的一样: - )

答案 1 :(得分:1)

假设BYTE是某种类型或typedef,sizeof(int) > sizeof(BYTE),而您的真实代码有一些忘记粘贴的#includeusing指令,那么您的程序以多种方式导致未定义的行为:

  1. 类型int的表达式用于写入BYTE类型的存储,这违反了严格别名规则。
  2. 即使我们暂时忽略(1),它也会尝试写入向量的存储结尾。
  3. 该计划违反了main必须具有int返回类型的规则。违反可诊断规则意味着生成的任何可执行文件都具有完全未定义的行为。
  4. 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;检查它们是否仍在那里。