gcc和clang产生不同的输出,而左移使用无符号值

时间:2016-03-04 20:53:42

标签: c++ c language-lawyer undefined-behavior

根据this interesting paper about undefined behavior optimization in c,当n = 0时,表达式(x<<n)|(x>>32-n)“在C中执行未定义的行为。 This stackoverflow discussion确认了负整数的行为未定义,并讨论了左移值的其他一些潜在缺陷。

请考虑以下代码:

#include <stdio.h>
#include <stdint.h>

uint32_t rotl(uint32_t x, uint32_t n)
{
    return (x << n) | (x >> (32 - n));
}

int main()
{
    uint32_t y = rotl(10, 0);
    printf("%u\n", y);
    return 0;
}

使用以下参数进行编译:{{1​​}}

  • 在gcc&gt; 5.1.0中,程序的输出为-O3 -std=c11 -pedantic -Wall -Wextra
  • 在clang&gt; 3.7.0中,输出为10

有趣的是,在使用c ++进行编译时仍然如此:gcc resultsclang results

因此,我的问题如下:

  1. 根据标准中的语言,我应该调用未定义/实现定义的行为,因为这两个参数都是无符号整数,并且没有一个值是负数。它是否正确?如果没有,c11和c ++ 11标准的相关部分是什么?
  2. 如果前一个语句为真,那么哪个编译器根据c / c ++标准生成正确的输出?直观地说,左移没有数字应该会返回值,即gcc输出。
  3. 如果不是上述情况,为什么没有警告此代码可能会因左移溢出而调用未定义的行为?

5 个答案:

答案 0 :(得分:7)

来自[expr.shift],强调我的:

  

如果右操作数,则行为未定义   为负,或大于或等于提升左操作数的位数

你在做:

(x >> (32 - n))

使用n == 0,因此您将32位数字右移32位。因此,UB。

答案 1 :(得分:2)

您的n0,因此执行x << 32 是未定义的行为,因为uint32_t 32位或更多位未定义。

答案 2 :(得分:1)

如果n为0,32-n为32,由于x有32位,x>>(32-n)为UB。

链接的SO帖子中的问题是不同的。这个与签名无关。

答案 3 :(得分:1)

该帖子的一部分没有完全回答。

  

为什么没有警告说这个代码可能会因左移溢出而调用未定义的行为?

查看add()代码,编译器应该警告什么?如果总和超出INT_MIN ... INT_MAX的范围,是UB吗?因为以下代码没有采取预防措施来防止溢出like here,它是否会发出警告?如果你这么想的话,那么有很多代码会对这个潜在的代码感到畏缩,那个程序员会很快关闭这个警告。

int add(int a, int b) {
  return a + b;
}

这里的情况差别不大。如果n > 0 && n < 32,则没有问题。

uint32_t rotl(uint32_t x, uint32_t n) {
  return (x << n) | (x >> (32 - n));
}

C创建快速代码主要是因为它缺少大量的运行时错误检查,并且编译器能够执行非常好的优化代码。如果需要大量的运行时检查,还有其他语言适合那些程序员。

C没有网络编码。

答案 4 :(得分:1)

当编写C标准时,某些实现在尝试以极大或负数量执行移位时会表现得很奇怪,例如:左移-1可能会占用中断禁用的CPU,而其微码将值移动40亿次,并且禁用那么长的中断可能会导致其他系统故障。此外,虽然很少有任何实现在完全按字长移动时会做任何特别奇怪的事情,但实现与返回的值不一致。有些人会把它当作零移位,而有些则会产生相同的结果,即换一个字大小的时间,有些则有时做一个,有时做另一个。

如果标准的作者指出精确地改变字大小可以在这两种可能的行为之间以非特定的方式选择,那将是有用的,但是标准的作者对指定所有的东西并不感兴趣编译器自然会在有或没有授权的情况下执行 。我不认为他们认为普通平台的实现不会自然地产生像上面给出的“旋转”这样的表达式的普通行为,并且不想让标准与这些细节混乱。

然而,今天,一些编译器编写者认为利用所有形式的UB进行“优化”比支持以前基本上所有普通实现都支持的有用自然行为更为重要。当y == 0时是否使“旋转”表达式出现故障将允许编译器生成比其他方式更小的有用程序是无关紧要的。