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

时间:2019-07-02 15:38:45

标签: 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;
}

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

gcc-9 -Werror -Wpedantic main.c

我收到此错误:

main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]

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

1 个答案:

答案 0 :(得分:14)

  

我收到此错误:

main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]
     

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

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

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

  

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

  

每个常数表达式的计算结果应为   类型的可表示值范围。

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

(((1 << 6) - 1) << ((((0 + 8) + 8) + 3) + 7))

...但是为了保持理智,让我们删除一些不必要的括号并简化外部<<的右侧以获取此信息,从而保留相关特征:

((1 << 6) - 1) << 26

所有单个数字常量的类型均为int,因此所有中间结果也是如此(其中简化版本中的“ 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;
}

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