在 gcc 中将二维数组初始化为 0 时的值不正确
Posted
技术标签:
【中文标题】在 gcc 中将二维数组初始化为 0 时的值不正确【英文标题】:Incorrect values when initializing a 2D array to 0 in gcc 【发布时间】:2019-02-06 17:29:09 【问题描述】:#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
我在https://www.codechef.com/ide中使用gcc 6.3
我希望第一行全为零。不应该是这样吗?
编辑:我用 const 变量对行和列进行了测试,然后将其初始化为全零。我觉得这应该引发编译错误,而不是表现出这种不正确(并且有潜在危险)的行为。
【问题讨论】:
这个int opt[rows][cols]
不是有效的 C++ - 数组大小必须是编译时常量,而不是变量。
C++ 不支持可变长度数组。如果您将rows
和columns
更改为const
,问题就解决了。
@dev_nut 请不要阅读 C 标签。这个问题与C无关...
我回滚了,因为有人在原始代码中将变量更改为 const,这使得整个问题变得毫无意义。不打算回滚标签。
有趣的是,在 wandbox 上进行测试,gcc 4.9.x 系列都产生了全零。上面没有和下面的所有内容都表示无法初始化数组。这可能是一个错误
【参考方案1】:
如果我们查看gcc 4.9 release notes,看起来他们添加了对初始化 VLA 的支持,并期望 VLA 在未来的 C++ 版本中得到支持:
G++ 支持 C++1y 可变长度数组。 G++ 长期以来一直支持 GNU/C99 风格的 VLA,但现在还支持初始化器和 lambda 引用捕获。在 C++1y 模式下,G++ 将抱怨标准草案不允许使用的 VLA,例如形成指向 VLA 类型的指针或将 sizeof 应用于 VLA 变量。请注意,现在看来 VLA 将不再是 C++14 的一部分,而是将成为单独文档的一部分,然后可能是 C++17。
我们可以看到it live that before 4.9 抱怨我们无法初始化 VLA
error: variable-sized object 'opt' may not be initialized
int opt[rows][cols] = 0;
^
但在4.9.1 and after 中,它不再抱怨,并且没有我们在最近的versions 中看到的相同错误。
所以它看起来像一个回归。
请注意,clang 拒绝允许初始化 VLA (which they support as an extension) see a live example。从C99 does not allow initialization of VLA 开始,这很有意义:
要初始化的实体的类型应该是一个未知大小的数组或一个对象类型不是变长数组类型。
gcc 错误 69517
gcc bug report :SEGV on a VLA with excess initializer elements 有一条评论提供了有关此功能的一些背景信息:
(从评论 #16 中回复 Jakub Jelinek)
这里的错误在于 G++ 中的 VLA 初始化程序接受的元素比 VLA 中的空间多,然后在运行时使用额外的元素丢弃堆栈。这是相对于 GCC 4.9.3 的回归,它实现了 n3639 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3639.html) 中指定的 C++ VLA。这记录在 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 ...
VLA 随后从 C++ 中删除,并且部分(但不是完全)从 G++ 中删除,这导致使用 G++ 4.9 开发和测试的 C++ 程序在移植到更高版本时会中断。
C++ VLA 与注释 #9 中引用的补丁一起使用会更安全。它的补丁必须从 GCC 6.0 恢复,因为它会导致 Java 出现问题。 Java 已被删除,我计划/希望重新提交 GCC 8 的补丁。(我想为 GCC 7 做,但没有做到。)
【讨论】:
【参考方案2】:这似乎是一个 GCC 错误,所需的行为很可能是它不应该编译。 C99 支持变长数组,但拒绝初始化它们:C 初始化器需要在编译时知道它们的类型,但变长数组的类型在编译时不能完整。
在 GCC 中,C++ 将可变长度数组作为其 C99 支持的扩展。因此,在 C++ 中控制可变长度数组初始化的行为不是由标准建立的。即使在 C++ 中,Clang 也拒绝初始化可变长度数组。
请注意,即使= 0
从技术上讲也有点危险(如果它确实有效的话):如果rows
和cols
为0,你就会溢出。 Memset 可能是你最好的选择。
【讨论】:
因为它是 C++,=
应该可以工作。但实际上它给了我internal compiler error
。 ://
请注意,在函数内部,C 允许在非静态(但大小固定)数组中使用非常量初始值设定项。此外,标准 C 和标准 C++ 都不允许零作为数组维度。这也是一个 GCC 扩展。
@HolyBlackCat,你如何推断=
应该工作?
需要与它正在初始化的事物具有相同的类型,并且该类型是未知的。
@zneak 好吧,它适用于一维 VLA。 “需要与它正在初始化的东西具有相同的类型” 不确定我是否理解。这是一个花括号初始化列表,所以我希望它没有类型。我们正在初始化的东西的类型是已知的,它是int[rows][cols]
(这似乎是一个“可变修改类型”,另一个 GCC 扩展)。
@HolyBlackCat,它在 C 中不起作用,也不是标准的 C++,而且在这一点上它也可以说在非标准 C++ 中也不起作用。确实,我混淆了实现细节 (Clang gives a type to initializer lists),但 C 支撑初始化器至少需要知道它们正在初始化的类型以支持指定的初始化器。我认为我不需要澄清为什么我将不完整的类型称为“未知”。【参考方案3】:
我发布这个问题是为了了解我的代码或 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
【讨论】:
或者更好的是,使用大小为rows * cols
的单个std::vector<int>
。
当然,这样缓存更一致。我喜欢双索引,因为它在处理 2D 矩阵、表格等时更直观。以上是关于在 gcc 中将二维数组初始化为 0 时的值不正确的主要内容,如果未能解决你的问题,请参考以下文章