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

时间:2015-10-19 22:39:41

标签: c++ arrays structure heap

我以为我知道如何处理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的最后一次打印会打印那些奇怪的数字。如果我像这样添加一个构造函数:

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对象是否会以某种方式正确构造? 为什么我需要显式指定数组大小,即使它将编译和链接只是没有?以这种方式在堆栈上初始化数组不会给我一个运行时错误(我测试它确定)。

2 个答案:

答案 0 :(得分:5)

这是一个错误:

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

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

4置于此处可使您的代码正常运行。

您的编译器显然有一个“扩展名”,将new A[]视为new A[1]

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

答案 1 :(得分:1)

  

为什么奇怪的数字?

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

  

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

如果没有构造函数,所有成员都将初始化为默认值。 int和大多数普通旧数据类型没有定义的默认值。在典型的实现中,它们获得已经在其分配的存储器块中发生的任何值。如果成员对象的类型不是默认构造函数并且无法生成,则会出现编译器错误。

  

为什么我需要显式指定数组大小,即使它将编译和链接只是没有?

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

  

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

在静态版本中,编译器可以计算初始化列表中的元素数。为什么带有新功能的动态版本必须说我没有好的答案。您认为计算初始化列表很简单,因此有更深层次的阻止它。辩论并批准该标准的民众要么从未考虑过以这种方式分配动态阵列,要么找不到能够在所有情况下都能运行的好方法。同样的原因,变量长度数组仍然不在标准中。

  

&#34;为什么我需要显式指定数组大小,即使它将编译和链接只是没有?它不应该编译,......&#34;需要明确的是:如果我将构造函数添加到A并运行它,它会运行得很好,直到delete []语句。只有这样它才会崩溃,但是cout&lt;&lt; c [0]作为&#39;期望&#39;

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

我的怀疑,这是基于具体的猜测,因为你已经深入到未定义的领域,delete[]的崩溃是因为

A* c = new A[]

分配A[1]并将其分配给c,而不是无法编译。 c有一个A可以使用。初始化列表尝试填充4并将3写入c[0],将4,5和6写入堆控制信息,删除需要将数据放回。所有看起来都很好,直到删除尝试使用覆盖的信息。

  

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

结构对数据封装的态度比类更宽松,默认为public访问,其中类默认为private。我从来没有尝试过,但我打赌你可以使用相同的结构技巧来初始化一个类的所有公共成员。

行。刚试了一下。适用于GCC 4.8.1。如果不在标准中查找,一般不会提出这种说法。得到它的副本。