堆上的结构数组没有正确初始化

Array of structs on heap not properly initialized

本文关键字:初始化 数组 结构      更新时间:2023-10-16

我以为我知道如何处理c++中的内存管理,但这让我感到困惑:

考虑以下代码:

struct A {
    int i;
};
int main(int argc, char* argv[]) {
    A a{ 5 }; //Constructs an A object on the stack
    A* b = new A{ 7 }; //Constructs an A object on the heap and stores a pointer to it in b
    A* c = new A[] { //Construct an array of A objects on the heap and stores a pointer to it in c
        { 3 },
        { 4 },
        { 5 },
        { 6 }
    };
    std::cout << "a: " << a.i << "n"; //Prints 'a: 5'
    std::cout << "b: " << b->i << "n"; //Prints 'b: 7'
    std::cout << "c: " << c[0].i << "; " << c[1].i << "; " << c[2].i << "; " << c[3].i << "n"; 
    //Prints 'c: -33686019; -1414812757; -1414812757; -1414812757'
    delete b;
    delete[] c;
    return 0;
}

我不明白为什么c最后打印出来的是那些奇怪的数字。如果我像这样给a添加一个构造函数:

struct A {
    A(int i) : i{i} {}
    int i;
};

那么最后一次打印输出变成:

'c: 3; 4; 5; 6'

应该是这样的。但是现在delete[] c;会给我一个运行时错误(似乎不是例外),说MyGame.exe has triggered a breakpoint.(我在VS2013中工作)。

此外,如果我将A* c = new A[] {行更改为A* c = new A[4] {,错误就会消失,一切都如预期的那样工作。

我的问题是:为什么这些奇怪的数字?如果我不定义构造函数数组中的A对象就不能正确构造吗?为什么我需要显式地指定数组的大小即使它可以正常编译和链接?以这种方式初始化堆栈上的数组不会给我一个运行时错误(我测试了它以确保)。

这是一个错误:

A* c = new A[] { {3}, {4}, {5}, {6} };

你必须把尺寸放在[]里面。对于new,数组的维度不能从初始化列表中推断出来。

4放在这里可以让你的代码正确地为我工作。

你的编译器显然有一个"扩展",将new A[]视为new A[1]

如果你在标准模式下编译(使用gcc或clang, -std=c++14 -pedantic),这总是一个好主意,编译器会告诉你这样的事情。将警告视为错误,除非您确定它们不是错误:)

为什么这些奇怪的数字?

因为没有分配内存来支持它们。指针指向克罗姆知道什么。这个结构不能编译。

如果我不定义构造函数,数组中的A对象不会以某种方式正确构造吗?

如果没有构造函数,所有成员都将初始化为其默认值。int和大多数Plain Old数据类型没有定义的默认值。在一个典型的实现中,它们获取恰好已经在其分配的内存块中的任何值。如果成员对象的类型没有默认构造函数,并且无法创建默认构造函数,则会得到编译器错误。

为什么我需要明确指定数组的大小,即使它将编译和链接很好没有?

不应该编译,数组的大小(未指定,对其本身来说是一个错误)和初始化列表中的元素数量不匹配,因此编译器有一个错误。

以这种方式初始化堆栈上的数组不会给我一个运行时错误(我测试了它以确保)。

在静态版本中,编译器可以计算初始化列表中元素的个数。为什么动态版带新不能,不得不说我没有好的答案。你可能会认为这只是简单的计算初始化列表,所以有更深层次的东西在阻止它。争论和批准该标准的人要么从未考虑过以这种方式分配动态数组,要么找不到一种让它在所有情况下都能工作的好方法。同样的原因,可变长度数组仍然不在标准中。

"为什么我需要明确地指定数组的大小,即使它可以编译和链接很好吗?它不应该编译,...."需要说明的是:如果我将构造函数添加到A中并运行它,在delete[]语句之前,它运行得很好。只有这样,它才会崩溃,但会退出<<C[0]工作正常

这是因为你运气不好。该构造函数写入程序拥有的内存,但没有分配给c。打印这些值是有效的,但是在那个时候应该在内存中的东西已经被覆盖了。这可能会导致您的程序迟早崩溃。这次是晚些时候。

我的怀疑,这是基于特定的猜测,因为你已经冒险进入了未定义的领域,delete[]上的崩溃是因为

A* c = new A[]

分配A[1]并将其分配给c而不是编译失败。c有一个A工作。初始化列表试图填充4,并将3写入c[0],并将4、5和6写入delete需要放回数据的堆控制信息。一切看起来都很好,直到delete试图使用覆盖的信息。

哦,还有这个:"如果没有构造函数,所有成员都将初始化为默认值。int型和大多数普通旧式数据类型没有定义的默认值。对于结构体,用户定义的actor似乎是可选的,因为您可以通过提供与其数据字段相对应的参数来初始化结构体。

结构体对数据封装的态度比类宽松得多,并且默认为public访问,而类默认为private。我从未尝试过,但我敢打赌,您可以使用相同的结构技巧来初始化类的所有公共成员。

OK。试一试。在GCC 4.8.1中工作。如果不查一下标准,我是不会笼统地这么说的。我得复制一份