为什么这个常数表达式不是常数

iva*_*ult 13 c gcc

我有以下C清单:

static const int constant = (0 | ((((1 << 6) - 1) << ((((0 + 8) + 8) + 3) + 7)) & ((1) << ((((0 + 8) + 8) + 3) + 7))) | ((((1 << 7) - 1) << (((0 + 8) + 8) + 3)) & ((0) << (((0 + 8) + 8) + 3))) | ((((1 << 3) - 1) << ((0 + 8) + 8)) & ((0) << ((0 + 8) + 8))) | ((((1 << 8) - 1) << 0) & ((1) << 0)));

int main(int argc, char** argv)
{
    return constant;
}

Run Code Online (Sandbox Code Playgroud)

当我尝试使用以下命令行使用GCC-9.1进行编译时:

gcc-9 -Werror -Wpedantic main.c
Run Code Online (Sandbox Code Playgroud)

我收到此错误:

main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]
Run Code Online (Sandbox Code Playgroud)

这是为什么?这是编译器错误吗?显然,constant使用常量表达式进行了初始化。

Joh*_*ger 14

我收到此错误:

main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]
Run Code Online (Sandbox Code Playgroud)

这是为什么?这是编译器错误吗?显然,常量是用常量表达式初始化的。

“常量表达”是语言标准中定义的术语。我怀疑GCC是以这种方式使用的,因为该标准确实要求您的初始化程序在这种意义上是一个常量表达式。当然,需要以此为基础对代码进行评估。

对常量表达式有两种语言限制:

常量表达式不得包含赋值,递增,递减,函数调用或逗号运算符,除非它们包含在未求值的子表达式中。

每个常量表达式的值都应等于其类型可表示值范围内的常量。

前者对您来说不是问题。但是,后者 C实现中的一个问题,其中类型int具有31个或更少的值位(包括大多数平台上的GCC)。特别考虑以下子表达式:

(((1 << 6) - 1) << ((((0 + 8) + 8) + 3) + 7))
Run Code Online (Sandbox Code Playgroud)

...但是为了理智,让我们删除一些不必要的括号并简化外部的右手边<<以获得这一点,它保留了相关特征:

((1 << 6) - 1) << 26
Run Code Online (Sandbox Code Playgroud)

所有单独的数字常量都具有type int,因此所有中间结果也都为type (简化版本中的“ 26”对应于原始表达式中的此类中间结果)。该左移的算术正确结果至少需要32个值位,并且由于您int(可能)没有那么多值位,因为为符号保留了一位,所以行为未定义。

因此,这里没有编译器错误,尽管您可能有抱怨实施质量的理由。同样,因此,没有任何接受警告而没有错误的代码的编译器都不会因此而出错。从另一种意义上讲,您的代码确实违反了语言约束,从这种意义上说,编译器有义务发出诊断信息,尽管它选择的诊断似乎具有误导性。

此外,其他人对该问题的评论似乎证实了溢出与错误相关,因为将被调出的表达式从使用(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;
}
Run Code Online (Sandbox Code Playgroud)

注意,按位移位结果的类型仅由其左操作数的类型确定。