编译器错误? g++ 允许可变大小的静态数组,除非函数是模板化的

Posted

技术标签:

【中文标题】编译器错误? g++ 允许可变大小的静态数组,除非函数是模板化的【英文标题】:Compiler bug? g++ allows variable-size static arrays, unless function is templated 【发布时间】:2012-06-06 23:14:54 【问题描述】:

下面的代码演示了我无法解释的 gcc 4.6.2 的行为。第一个函数声明了一个 vec_t 类型的静态数组,其中 vec_t 是 unsigned char 的 typedef 别名。第二个函数是相同的,只是 vect_t 的类型是一个模板参数。第二个函数编译失败,诊断为“错误:‘bitVec’的存储大小不是常数”。

#include <limits>

void bitvec_func()

    const std::size_t       nbits = 1e7;
    typedef unsigned char   vec_t;
    const std::size_t       WLEN  = std::numeric_limits<vec_t>::digits;
    const std::size_t       VSIZ  = nbits/WLEN+1;
    static vec_t            bitVec[nbits/VSIZ];    // Compiles fine


template <typename T>
void bitvec_func()

    const std::size_t       nbits = 1e7;
    typedef T               vec_t;
    const std::size_t       WLEN  = std::numeric_limits<vec_t>::digits;
    const std::size_t       VSIZ  = nbits/WLEN+1;
    static vec_t            bitVec[nbits/VSIZ];    // "error: storage size of ‘bitVec’ isn’t constant"


void flarf()

    bitvec_func();
    bitvec_func<unsigned char>();

在我看来,使用参数 实例化模板应该会导致编译器生成与第一个函数相同的代码。任何人都可以提供任何见解,说明为什么情况似乎并非如此?

[附录:第二个函数使用“-std=c++0x”或“-std=gnu++0x”编译,但我仍然想了解如何/如果在早期的语言定义下是错误的。]

预计到达时间: 如果 nbits 的初始值设定项改变,第二个函数将编译:

const std::size_t       nbits = 1e7;              // Error
const std::size_t       nbits = (std::size_t)1e7; // Okay
const std::size_t       nbits = 10000000.0;       // Error
const std::size_t       nbits = 10000000;         // Okay

换句话说,如果nbits 是用整数类型的表达式初始化的,那么nbitsbitVec 的定义中被视为常量。如果 nbits 改为使用浮点表达式初始化,则编译器不再将其视为 bitVec 维度的表达式中的常量,并且编译失败。

在 C++ 中调用“编译器错误”比在 C 中要舒服得多,但我想不出任何其他原因使上述 4 种情况在语义上不完全相同。还有人愿意发表意见吗?

【问题讨论】:

您能否发布导致编译器错误的确切代码?我似乎无法重现它。 这就是上面的确切代码。编译器是 gcc 4.6.2,选项是“-O0 -g3 -c”。 在较旧的 gcc 4.3.4 上,此代码 compiled fine. 有趣。我逐字复制了您的代码并在 4.6.2 上重现了该错误。也许这是一个相对较新的“功能” :) 感谢您的检查。 对不起,我之前没有意识到,但你想用 nbits = 1e7; 做什么?如果您尝试将十六进制值分配给无符号整数,那将不起作用(另外,我认为这是问题所在)。 【参考方案1】:

在 gcc 4.7.0 上使用 -ansi 编译您的代码后,我能够重现此警告:

warning: ISO C++ forbids variable length array 'bitVec' [-Wvla]

both bitVec 出现此警告,而不仅仅是模板函数中的警告。然后我意识到nbits = 1e7; 行将double 分配给unsigned int。我认为正因为如此,由于某种原因导致nbits 不是一个常量表达式。您的代码为非模板版本编译的原因是因为 gcc 的可变长度数组扩展。此外,出于某种原因,您的 gcc 版本不允许在函数模板中使用可变长度数组。要修复您的代码,请将 1e7; 更改为 10000000

编辑

我向another question询问了规则。答案是在 C++03 中代码无效,但在 C++11 中是可以的。

【讨论】:

它并没有丢失它的const 属性,而是(隐含的)constexpr 属性。 @MooingDuck:确实如此(已更正)。但是,我仍然想知道为什么会这样。 1e70x1e7 是非常不同的数字。 10000000 会是更好的替代品。 如上所述,1e7 (10,000,000) 是预期的。这似乎是问题的症结所在。 IANAL,但我的理解是 int i = 123.4 会将浮点常量转换为 int 并使用结果来初始化 i。但是,将“nbits”初始值设定项从1e7 更改为10000000 甚至(std::size_t)1e7 可以消除错误。 @JohnA:请参阅我的答案中的链接。在 C++03 中,int i = 123.4 无效,但在 C++11 中规则改变了,没关系。【参考方案2】:

答案当然是在产生错误的编译阶段,数组索引的存储大小不是恒定的——即它位于模板扩展的上游。动态数组不是 C++ 98/03 的一部分,它们是 gcc 扩展(最初是 C)。所以这个错误实际上是正确的,即使实现看起来很奇怪。据推测,GCC 会为模板化数组找到符合标准的路径,在需要时支持它们,否则会抛出错误,但静态类型的数组会到达“C”路径,因此会自动选择 gcc 扩展。

【讨论】:

代码实际上没有变量数组,所以错误是错误。数组的大小在编译时是已知的,并且应该由符合标准的编译器来处理。 这不仅仅是运行时可变性的问题。我相信(但我懒得检查),C++03 不允许在数组大小初始化器中专门使用模板参数(正是因为在正确的时间扩展它们的鸡与蛋问题)。毫无疑问,对规范有更深入了解的人会来纠正我。 有趣。这个(混乱)有效:template &lt;typename T&gt; void a() typedef T vec_t; static vec_t bitVec[sizeof(vec_t)*8];

以上是关于编译器错误? g++ 允许可变大小的静态数组,除非函数是模板化的的主要内容,如果未能解决你的问题,请参考以下文章

为什么不能使静态数组的大小变量? [重复]

g++(4.7.2)错误或功能,在编译时初始化静态数组时?

用指针创建静态数组?

变长 std::array 像

JAVA堆内存和栈内存初步了解

Heap and Stack