我以为自己发现了 clang 的一个 bug……

摘要:完成体系结构课程作业时遇到 gcc 和 clang 的行为不一样。确切地说是 clang -O1 及以上会得到意外的结果。以为自己搞了个大新闻,结果发现是自己写了 ub(未定义行为,undefined behavior)。

代码如下。

#include <stdio.h>
#include <limits.h>
int test(int x, int n)
{
  int y = (1 << (n - 1)) - 1;
  int le = (x <= y);
  printf("%d, %d, %d\n", x, y, le);
  return 0;
}

int main(){
  int n = sizeof(int) * 8;
  test(INT_MIN, n);
  return 0;
}

gcc 版本:gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
输出的结果是:

$ gcc bar.c -O1 -o g && ./g
-2147483648, 2147483647, 1

clang 版本:Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
输出的结果是:

$ clang bar.c -O1 -o c && ./c
-2147483648, 2147483647, 0

 

好奇怪啊,明明一个正数、一个负数,怎么 clang -O1 就负数更大了呢???

是不是 clang 编译优化的 bug?

——我也以为自己搞了个大新闻。仔细研究一番便发现,这里并不能怪编译器:1 << 31 已经发生了溢出;即使得到了负值 INT_MIN,在其上再减去 1,再次发生溢出。对于有符号整数运算溢出,尽管很常见的做法是截断取模,但实际上标准并没有作出规定。因此,从这一行起,编译器做什么都是符合标准的( ̄(工) ̄)

ub 很可能直接被编译器优化掉[1]。此处就是这样。可以从汇编码看出,开启编译优化时,clang 把 le 参数直接优化掉了,大意如此:

printf("%d, %d, %d\n", x, y, le); //clang -O0
printf("%d, %d, %d\n", x, y, 0);  //clang -O1

完。

3 thoughts on “我以为自己发现了 clang 的一个 bug……”

  1. 最近从[某 OIer 那儿](https://chestnut.cf/2016/12/13/noip2016真·滚粗记/)知道了一个,跟你碰到的这个情形“对偶”的情形:
    32位linux,g++的环境下,double x=2,则 (int)(log(2)/log(2))==0。其中log是里的std::log。
    如果开g++ -O1,或换64位系统,或double x=3,就都可以 (int)(log(x)/log(x))==1。

    clang、浮点数、不开-O1出事,跟g++、整数、开-O1出事,正好对偶……

    翻c++ standard没看出个门道,问 [FrankHB](https://github.com/FrankHB) 他说 “c++ standard 不保证 (int)(log(x)/log(x))==1” 。所以大概也是个UB。

发表评论

电子邮件地址不会被公开。 必填项已用*标注