为什么这种转变的结果会被视为未定义?

时间:2014-02-20 13:33:46

标签: c language-lawyer clang-static-analyzer

我正在使用C90和C99的混合物(不能完全使用C99,原因我最好不要讨论,因为它们对我的血压不利并且会危及阻止我们移动我们的人的生命代码库进入当前的千年)。我仍然会引用C99标准。

我的代码大致是这样的,当压缩到最低限度(test.c)时:

#include <stdio.h>

unsigned int foo(unsigned int n)
{
    unsigned int x, y;
    n = n - 264;
    x = (n >> 2) + 1;
    y = 1U << (x + 2U);
    return y;
}

int main(void)
{
    printf("%u\n", foo(384));
    return 0;
}

当然,传递给foo()的值可能会大于此处给出的值。仍然是384是触发Clang静态分析器(从发布标签编译的3.4)发出警告的最低值:

$ clang -cc1 -triple x86_64-unknown-linux-gnu -analyze -analyzer-checker=core -internal-isystem /usr/local/include -internal-isystem $HOME/bin/LLVM/bin/../lib/clang/3.4/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O0 -x c test.c
test.c:8:9: warning: The result of the '<<' expression is undefined
        y = 1U << (x + 2U);
            ~~~^~~~~~~~~~~
1 warning generated.

现在逐一浏览:

// n == 384
n = n - 264;        // n := 384 - 264
// n == 120
x = (n >> 2) + 1;   // x := (120 div 4) + 1
// x == 31
y = 1U << (x + 2U); // y := 1 << 33

所以,好吧它将所有有意义的位从整数中推出,并且根据我对以下内容的理解(来自here),这应该只给我零:

  

6.5.7按位移位运算符

     

...

     

4

     

E1 << E2的结果是E1左移E2位位置;腾出的位用零填充。如果E1有未签名的   类型,结果的值是E1 × 2^E2,减去一个模数   比结果类型中可表示的最大值。如果E1有   签名类型和非负值,E1 × 2^E2可表示   结果类型,那就是结果值;否则,   行为未定义。

根据我的阅读方式,如果涉及签名值,则只能发生未定义的结果。但是,我注意到所有的值都是无符号的,甚至在文字上都是明确的。

我错了还是Clang静态分析仪过于热心?


此代码的原始版本来自C ++中的Jonathan Bennetts JB01实现(版本1.40a)。

2 个答案:

答案 0 :(得分:7)

在C99标准中,就在您引用的部分之前:

  

3

     

对每个操作数执行整数提升。结果的类型是   升级的左操作数。如果右操作数的值为负或为   大于或等于提升左操作数的宽度,行为未定义。

今天大多数机器中的

unsigned int都有32位,这使得左移33,未定义的行为。

答案 1 :(得分:3)

同一段也在你引用的部分之前,在第6.5.7.3段中说:

  

如果右操作数的值为负或者是   大于或等于提升的左操作数的宽度,行为是未定义的。

因此,clang做得很好,因为一旦你移动的位数比提升的左操作数可以容纳的话,行为确实是未定义的。