【问题标题】:Array of structs on heap not properly initialized堆上的结构数组未正确初始化
【发布时间】:2016-01-18 10:23:37
【问题描述】:

我以为我知道如何在 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*)而不是数组。从中读取会调用未定义的行为。
  • here 编译失败!
  • 奇怪的数字是因为你的编译器有点坏。 g++ 5.1 说“错误:']' 标记之前的预期主表达式”和“错误:'A [1]' 的初始化程序太多”。 VS 2015 说“错误 C3078:必须在新表达式中指定数组大小”。 VS2013 对该代码的影响尚不清楚。

标签: c++ arrays structure heap-memory


【解决方案1】:

为什么会有奇怪的数字?

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

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

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

为什么我需要明确指定数组大小,即使没有它也可以编译和链接?

它不应该编译,数组的大小(未指定且本身有错误)与初始化列表中的元素数量不匹配,因此编译器存在错误。此时不涉及链接器。

以这种方式初始化堆栈上的数组不会给我带来运行时错误(我测试过它是肯定的)。

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

“为什么我需要明确指定数组大小,即使它可以编译和链接就好了?它不应该编译,...。”要清楚:如果我将构造函数添加到 A 并运行它,它运行得很好,直到 delete[] 语句。只有这样它才会崩溃,但 cout

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

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

A* c = new A[]

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

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

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

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

【讨论】:

  • “为什么我需要显式指定数组大小,即使它可以编译和链接就好了?它不应该编译,...。”要清楚:如果我添加构造函数到 A 并运行它,它运行得很好,直到 delete[] 语句。只有这样它才会崩溃,但 cout
  • 哦,还有这个:“如果没有构造函数,所有成员都将被初始化为其默认值。int 和大多数普通旧数据类型没有定义的默认值。”。对于结构,用户定义的 ctor 似乎是可选的,因为您可以通过提供与其数据字段对应的参数来初始化结构。
  • @BitJunky 无法在评论中正确回答这些问题。将它们添加到答案中。
【解决方案2】:

这是一个错误:

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

您必须将维度放在[] 中。使用new,数组维度不能从初始化列表中推导出来。

4 放在这里可以使您的代码对我正常工作。

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

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

【讨论】:

  • 要学究气,你所需要的只是-std=c++11 和识别该选项的gcc 或clang 版本。您不需要升级到 c++14,也不需要-pedantic。 Clang(正确地)拒绝new A[] {...},因为根据标准,方括号内应该有一个表达式。 gcc 不会拒绝new A[],因为它将其解释为new A[1]。但是,它确实对三个额外的初始化程序犹豫不决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-20
  • 1970-01-01
  • 2016-08-13
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 2015-07-29
相关资源
最近更新 更多