I am getting this error:
main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]
Why is that? Is this a compiler bug? Clearly, constant is initialized
with a constant expression.
"常量表达式"是语言标准中的
定义术语。我怀疑GCC也是这样使用它的,因为标准确实要求您的初始化器在这个意义上是一个常量表达式。当然,需要以这种方式执行代码的评估。
常量表达式有两个语言约束:
常量表达式不得包含赋值、增量、减量、函数调用或逗号运算符,除非它们包含在不被评估的子表达式中。
每个常量表达式应该评估为其类型可表示值范围内的常量。
前者对您来说不是问题。然而,后者在C实现中存在问题,其中类型int具有31位或更少的值位(包括大多数平台上的GCC)。特别考虑此子表达式:
(((1 << 6) - 1) << ((((0 + 8) + 8) + 3) + 7))
...但是为了更加简化,让我们去掉一些不必要的括号并简化外部<<
右侧的内容,以保留相关特性:
((1 << 6) - 1) << 26
所有的数字常量都有类型int,因此所有的中间结果也是如此(在简化版本中的“26”对应原始表达式中的这样一个中间结果)。那个左移的算术正确的结果需要至少32个值位,并且由于你的int(可能)没有那么多,因为一位被保留用于符号,所以行为未定义。
因此,这里没有编译器bug,尽管您可能对实现质量有投诉的理由。同样,任何接受代码而不发出警告或错误的编译器都不是因为存在错误。在不同意义上,您的代码确实违反了语言约束,在这种意义上,编译器有责任发出诊断,尽管它选择的那个似乎具有误导性。
此外,其他人对问题的评论似乎证实了溢出与错误有关,因为将被调用的表达式从使用(1 << 6)更改为(1 << 5)或(1u << 6)可以解决其他能够复制该问题的人的错误。两者都产生没有任何未定义行为的总体表达式。
请注意,当您进行位操作时,避免使用有符号整数类型通常是更好的选择。因此,在忽略从中提取此内容的更大程序的任何影响的情况下,我倾向于重写您的示例程序如下:
static const unsigned int constant = (0
| ((((1u << 6) - 1) << ((((0 + 8) + 8) + 3) + 7)) & ((1u) << ((((0 + 8) + 8) + 3) + 7)))
| ((((1u << 7) - 1) << (((0 + 8) + 8) + 3)) & ((0u) << (((0 + 8) + 8) + 3)))
| ((((1u << 3) - 1) << ((0 + 8) + 8)) & ((0u) << ((0 + 8) + 8)))
| ((((1u << 8) - 1) << 0) & ((1u) << 0)));
int main(void) {
// There's a potential issue with the conversion of the return value, too, but it
// does not affect the particular expression at issue here.
return constant;
}
注意,按位移操作的结果类型仅由其左操作数类型决定。
int
的范围吗?(请注意,clang 没有抱怨。)可能是 gcc 的一个 bug。 - Keith Thompson(1 << 6)
更改为(1 << 5)
,GCC 9.1 是否仍会拒绝该代码? - John Bollinger(1 << 6)
改为(1 << 5)
,错误消息就会消失。这个术语变成了((((1 << 5) - 1) << ((((0 + 8) + 8) + 3) + 7)) & ((1) << ((((0 + 8) + 8) + 3) + 7)))
,当你将6改为5时,避免了将1
左移32位(总共左移31位)。编译器可能是在一些方面很好但某些方面则较差的混合体。 - Jonathan Leffler