C++ 施放时如何分配内存?

C++ 施放时如何分配内存?,c++,C++,我们可以通过将连续内存块强制转换到对象中来创建对象。如果我们创建一个内存很少的对象,它在访问时不会崩溃。但它在超出范围时崩溃了。为什么? class A { public: int i; }; void main(int argc, char * argv[]) { { vector<BYTE> v(1); A* a = (A*)v.data(); a->i = 70000; //

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

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";
}
假设BYTE是某种类型或typedef,因此sizeofint>sizeofBYTE,并且您的实际代码中有一些include和using指令忘记粘贴,那么您的程序会以几种方式导致未定义的行为:

int类型的表达式用于写入BYTE类型的存储器,这违反了严格的别名规则。 即使我们暂时忽略1,它也会尝试写入向量存储的末尾。 程序违反了main必须具有int返回类型的规则。违反可诊断规则意味着生成的任何可执行文件都具有完全未定义的行为。 一旦发生,任何事情都可能发生。这包括但不限于碰撞和非碰撞。您不应期望出现任何特定事件或缺少此类事件。

假设字节是某种类型或typedef,因此sizeofint>sizeofBYTE,并且您的实际代码中有一些include和using指令忘记粘贴,那么您的程序会以几种方式导致未定义的行为:

int类型的表达式用于写入BYTE类型的存储器,这违反了严格的别名规则。 即使我们暂时忽略1,它也会尝试写入向量存储的末尾。 程序违反了main必须具有int返回类型的规则。违反可诊断规则意味着生成的任何可执行文件都具有完全未定义的行为。
一旦发生,任何事情都可能发生。这包括但不限于碰撞和非碰撞。你不应该期待任何特定的事件或缺少这些事件。

欢迎来到未定义的行为领域

a->i=70000;完全未定义=>您在只有一个字节的内容中写入一个int。所以你不知道会发生什么:

很好 分段故障 工作很好,但打破一些背景接缝,以你的情况 另一种不可预测的行为 仅凭猜测,我怀疑a->I=70000;将产生缓冲区溢出,这将覆盖向量中的一些内部变量。所以在分配过程中,没有明显的问题,因为已经分配了足够的内存。但当向量被破坏时,内部变量会产生错误的行为,从而导致崩溃


再一次,这只是猜测,因为这是未定义的行为。只有使用调试器,您才能观察崩溃前发生的情况。

欢迎来到未定义的行为领域

a->i=70000;完全未定义=>您在只有一个字节的内容中写入一个int。所以你不知道会发生什么:

很好 分段故障 工作很好,但打破一些背景接缝,以你的情况 另一种不可预测的行为 仅凭猜测,我怀疑a->I=70000;将产生缓冲区溢出,这将覆盖向量中的一些内部变量。所以在分配过程中,没有明显的问题,因为已经分配了足够的内存。但当向量被破坏时,内部变量会产生错误的行为,从而导致崩溃


再一次,这只是猜测,因为这是未定义的行为。只有使用调试器,您才能观察崩溃前发生的情况。

您的程序不会崩溃。堆损坏是编译器添加的额外警告

现在,当对象被销毁时,它会检查内存是否仍然正常。因为您将一个比预期更大的对象放入内存,所以该对象之前的内存被覆盖。隐式运行时通过编译器触发器进行检查,并给出此错误以告诉您内存有问题

当这种情况发生时,编译器依赖于内存检查。你可以打开内存检查,然后你不会在那里得到错误,但你仍然有损坏的内存,可能有一些奇怪的未定义的行为以后


此检查有几种方法。通常在分配内存的末尾添加额外的特殊字节,有时也在分配内存的前面添加一个on delete检查它们是否仍然存在。

您的程序不会崩溃。堆损坏是编译器添加的额外警告

现在,当对象被销毁时,它会检查内存是否仍然正常。因为您将一个比预期更大的对象放入内存,所以该对象之前的内存被覆盖。隐式运行时通过编译器触发器进行检查,并给出此错误以告诉您内存有问题

当这种情况发生时,编译器依赖于内存检查。你可以打开内存检查,然后你不会在那里得到错误,但你仍然有损坏的内存,可能有一些奇怪的未定义的行为以后

此检查有几种方法。通常在末尾添加额外的特殊字节,有时也在前面
从形式上看,您只是在调用未定义的行为,因为您试图以a*的形式访问字节*。根据严格的别名规则,编译器只需使用它想要的任何东西

在现实世界中,它将取决于执行情况,并且很可能给出正确的结果。在后台,向量的常见实现为其数据成员使用分配的内存。这意味着:

对于任何类型,它都将正确对齐 由于对齐,实际分配的内存可能至少足以容纳int,在32位系统上至少为4字节,在64位系统上至少为8字节 您可以安全地更改分配内存的类型 这意味着,这段代码不会崩溃并不令人惊讶,即使它是明显的未定义行为

注意:除了在非常特殊的情况下,以及在红色闪烁字体的通知中,您永远不应该依赖代码的实现细节,因为它可能会在下一版本的编译器中崩溃,或者只是在更改构建选项时崩溃


堆损坏只是一个警告,因为您的实现在它提供给您的最后一个字节后放置了一个标记,并且发现该标记被覆盖。但从低级的角度来看,您并没有删除任何重要的内容—假设您的实现与我的一样:-

从正式的角度来看,您只是在调用未定义的行为,因为您试图将字节*作为a*访问。根据严格的别名规则,编译器只需使用它想要的任何东西

在现实世界中,它将取决于执行情况,并且很可能给出正确的结果。在后台,向量的常见实现为其数据成员使用分配的内存。这意味着:

对于任何类型,它都将正确对齐 由于对齐,实际分配的内存可能至少足以容纳int,在32位系统上至少为4字节,在64位系统上至少为8字节 您可以安全地更改分配内存的类型 这意味着,这段代码不会崩溃并不令人惊讶,即使它是明显的未定义行为

注意:除了在非常特殊的情况下,以及在红色闪烁字体的通知中,您永远不应该依赖代码的实现细节,因为它可能会在下一版本的编译器中崩溃,或者只是在更改构建选项时崩溃


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

您使用的向量在堆上分配其内部缓冲区

在1字节缓冲区上写入4字节会导致缓冲区溢出,从而损坏堆


当作用域关闭时,向量的析构函数将释放其内部缓冲区,检测到堆损坏。

正在使用的向量将在堆上分配其内部缓冲区

在1字节缓冲区上写入4字节会导致缓冲区溢出,从而损坏堆


当作用域关闭时,向量的析构函数将释放其内部缓冲区,检测到堆损坏。

强制转换不会分配内存。它只是对现有内存进行不同的解释。因此,将较小的数据放入较大的内存中是没有问题的。另一方面,较小的内存中较大的数据不适合,这会导致错误。将较大的数据放入较小的内存是一种错误。是的,在你的情况下,这个程序不会崩溃,但它只是一个普通的幸运。你能提供字节的详细信息吗?是typedef无符号字符字节;我使用了typedef std::位集字节;在VS2015中没有崩溃,C++中没有崩溃的概念。只要你的程序的行为是由C++语言定义的,它就不会崩溃。“KerrekSB,现在……另外,您如何描述由于堆栈溢出而崩溃的程序的行为?根据该标准,任何由于堆栈溢出而无法执行程序的实现都是不符合标准的,因为casting不分配内存。它只是对现有内存进行不同的解释。因此,将较小的数据放入较大的内存中是没有问题的。另一方面,较小的内存中较大的数据不适合,这会导致错误。将较大的数据放入较小的内存是一种错误。是的,在你的情况下,这个程序不会崩溃,但它只是一个普通的幸运。你能提供字节的详细信息吗?是typedef无符号字符字节;我使用了typedef std::位集字节;在VS2015中没有崩溃,C++中没有崩溃的概念。只要你的程序的行为是由C++语言定义的,它就不会崩溃。“KerrekSB,现在……另外,您如何描述由于堆栈溢出而崩溃的程序的行为?根据标准,任何未能执行程序的实现
由于堆栈溢出是不一致的,他问为什么它只发生在堆栈的末尾。不太清楚为什么它一般会崩溃。@Hayt我的最后一段谈到了这一点-任何特定事件都包括崩溃或任何特定点上的其他行为。不过,这里的确切原因可能是编译器插入的诊断。他问为什么它只发生在堆栈的末尾。一般来说,这并不是它崩溃的真正原因。@Hayt我的最后一段谈到了这一点-任何特定事件都包括崩溃或任何特定点的其他行为。不过,这里的确切原因可能是编译器插入诊断。