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

Array of structs on heap not properly initialized

我以为我知道如何在 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 对象是否会以某种方式正确构造? 为什么我需要显式指定数组大小,即使它会编译并且 link 没有编译也很好?以这种方式初始化堆栈上的数组不会给我运行时错误(我测试过它是肯定的)。

这是一个错误:

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

您必须将维度放在 [] 内。使用 new 时,无法从初始化列表中推导出数组维数。

在这里输入 4 可以使您的代码对我来说正常工作。

您的编译器显然有一个 "extension" 将 new A[] 视为 new A[1]

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

Why the weird numbers?

因为没有分配内存来支持它们。指针指向 Crom 知道什么。该结构不应编译。

Won't the A objects in the array get properly constructed somehow if I don't define a constructor?

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

And why do I need to specify the array size explicitly even though it will compile and link just fine without?

它不应该编译,数组的大小(未指定和自身错误)与初始化列表中的元素数量不匹配,所以编译器有一个错误。此时不涉及链接器。

Initializing arrays on the stack this way does not give me a runtime error (I tested it to be sure).

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

"And why do I need to specify the array size explicitly even though it will compile and link just fine without? It shouldn't compile, ...." To be clear: If I add the constructor to A and run it, it runs just fine up until the delete[] statement. Only then it crashes but cout << c[0] works as 'expected'

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

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

A* c = new A[]

已分配 A[1] 并将其分配给 c 而不是编译失败。 c 有一个 A 可以使用。 initializer list 尝试填充 4 并将 3 写入 c[0] 和 4,5, 6 over the heap control information that delete 需要将数据放回。在删除尝试使用被覆盖的信息之前,一切看起来都很好。

Oh and this:"Without a constructor all of the members will be initialized to their defaults. int's and most Plain Old Datatypes have no defined default value.". For structs a user defined ctor seems optional because you can initialize a struct by providing arguments corresponding to its data fields.

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

好的。刚试过了。适用于 GCC 4.8.1。如果不在标准中查找,一般不会提出该声明。必须得到它的副本。