使用gcc将二维数组初始化为0时出现错误的值

13
#include <iostream>
using namespace std;

int main() {

    int rows = 10;
    int cols = 9;
    int opt[rows][cols] = {0};

         for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                std::cout << opt[i][j] << " ";
            }
             std::cout << "\n";
         }

    return 0;
}

输出:

0 32767 1887606704 10943 232234400 32767 1874154647 10943 -1 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 

我正在使用gcc 6.3,在https://www.codechef.com/ide

我希望第一行都是零。这不应该是这种情况吗?

编辑:我测试了行和列的常量变量,然后它初始化为全零。我觉得这应该抛出编译错误,而不是展示这种不正确(并且可能危险)的行为。


12
这个 int opt[rows][cols] 不是有效的 C++ 代码 - 数组大小必须是编译时常量,而不是变量。 - user2100815
6
C++不支持可变长度数组。如果你将“行”和“列”更改为“const”,则可以解决问题。 - imreal
6
请不要重新添加C标签。该问题与C无关。 - HolyBlackCat
8
我进行了回滚,因为有人在原始代码中将变量更改为const,这使整个问题毫无意义。我并不想回滚标签。 - dev_nut
1
即使在标准的 C 语言中,你也不能初始化一个变量大小的数组——我很惊讶这段代码能够编译通过(但它一定是 GCC 的扩展功能)。 - Jonathan Leffler
显示剩余9条评论
3个回答

13

如果我们查看gcc 4.9发行说明,看起来他们增加了对变长数组的初始化支持,并期望在未来版本的C++中支持变长数组:

G++支持C++1y变长数组。G++长期以来已经支持GNU/C99风格的VLAs,但现在还支持初始化器和引用捕获。在C++1y模式下,G++将抱怨不允许使用VLA的草案标准,例如形成指向VLA类型的指针或将sizeof应用于VLA变量。请注意,现在似乎VLAs将不是C++14的一部分,而将是一个单独的文档的一部分,然后可能是C++17。

我们可以通过实时演示看到,在4.9之前,我们无法初始化VLA。

error: variable-sized object 'opt' may not be initialized  
     int opt[rows][cols] = {0};  
                             ^

但在4.9.1版本及之后,它不再抱怨,并且没有我们在更多的版本中看到的相同的bug。

所以这看起来像是一个回归性问题。

请注意,clang拒绝允许VLA初始化(尽管他们支持这个扩展)请看一个实例。这是有道理的,因为C99不允许对VLA进行初始化

要初始化的实体类型应该是未知大小的数组或者是一个不是可变长度数组类型的对象类型。

gcc Bug 69517

gcc的错误报告:在带有额外初始化元素的VLA上SEGV有一条评论提供了一些关于这个特性的背景:

(在Jakub Jelinek的第16条评论中回复)

这里的错误在于G++接受了一个VLA初始化器,其中有比VLA空间更多的元素,并且在运行时使用额外的元素破坏堆栈。这是相对于实现C++ VLAs的GCC 4.9.3而言的一个退化(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3639.html)。这在GCC 4.9变化中已经文档化(https://gcc.gnu.org/gcc-4.9/changes.html),并使用以下示例突出显示了此功能:

  void f(int n) {
    int a[n] = { 1, 2, 3 }; // throws std::bad_array_length if n < 3
    ...

随后,C++中的可变长度数组(VLAs)被移除,同时也在G++中部分地(但不完全)移除,这导致使用G++ 4.9开发和测试的C++程序在移植到更高版本时出现问题。

使用评论 #9 中引用的patch,C++ VLAs 将变得更加安全。该补丁已经从GCC 6.0中回退,因为它会在Java中引起问题。 Java已被移除,我打算/希望将该补丁重新提交给GCC 8。(我本想为GCC 7做这件事,但没有时间。)


5
这似乎是GCC的一个bug,期望的行为很可能是不能编译。C99支持可变长度数组,但拒绝对其进行初始化:C的初始化程序需要在编译时知道其类型,但可变长度数组的类型无法在编译时完全确定。
在GCC中,C++从其C99支持中获得了可变长度数组扩展。因此,在C++中管理可变长度数组初始化的行为没有通过标准来建立。Clang即使在C++中也拒绝初始化可变长度数组。
请注意,即使使用= {0}也有技术上有些危险(如果能正常工作):如果rowscols都是0,你将会溢出。最好使用memset。

1
由于这是C++,= {}应该可以工作。但实际上它给了我“内部编译器错误”。 :/ - HolyBlackCat
请注意,在函数内部,C允许在非静态(但固定大小)数组中使用非常量初始化器。此外,标准C和标准C++都不允许将零作为数组维度;这也是GCC的扩展功能。 - Jonathan Leffler
2
@HolyBlackCat,你是怎么推断={} 应该可以工作的? {}需要与它初始化的东西具有相同的类型,而该类型是未知的。 - zneak
@zneak 嗯,它适用于1D VLAs。 “需要与其初始化的东西具有相同的类型”我不确定我是否理解。这是一个大括号初始化列表,所以我希望它没有类型。我们正在初始化的事物的类型是已知的,它是int [rows] [cols](似乎是另一个GCC扩展名为“可变修改类型”的类型)。 - HolyBlackCat
@HolyBlackCat,它在C中不起作用,也不是标准的C++,而且可以说在非标准的C++中现在也不起作用。虽然我混淆了实现细节(Clang为初始化列表提供了一种类型),但是C大括号初始化器需要知道它们正在初始化的类型以支持指定的初始化器,至少如此。我认为我不需要澄清为什么我称一个不完整的类型为“未知”。 - zneak

1

我发布了这个问题以了解我的代码或gcc出了什么问题。但是,在C++中,我会使用向量来满足可变长度数组的要求,而不是使用数组。

#include <iostream>
#include <vector>

int main() {

    int rows = 10;
    int cols = 9;

    std::vector<std::vector<int>> opt(rows, std::vector<int>(cols, 0));

         for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                std::cout << opt[i][j] << " ";
            }
             std::cout << "\n";
         }

    return 0;
}

输出:

0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 

2
甚至更好的方法是,使用一个大小为“行数*列数”的单个std::vector<int> - HolyBlackCat
当然,这样会更加缓存一致。我喜欢双重索引,因为在处理二维矩阵、表格等时更直观。 - dev_nut

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接