gcc 和 clang 在使用无符号值左移时产生不同的输出
gcc and clang produce different outputs while left-shifting with unsigned values
根据 this interesting paper about undefined behavior optimization in c, the expression (x<<n)|(x>>32-n)
"performs undefined behavior in C when n = 0". This Whosebug 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;
}
使用以下参数编译:-O3 -std=c11 -pedantic -Wall -Wextra
- 在 gcc >5.1.0 中,程序的输出是
10
。
- 在 clang >3.7.0 中输出是
4294967295
.
有趣的是,用 c++ 编译时仍然如此:gcc results, clang results.
因此,我的问题如下:
- 根据我对标准语言的理解,这应该 不 调用未定义/实现定义的行为,因为两个参数都是无符号整数并且 none值为负。这个对吗?如果不是,c11 和 c++11 标准的相关部分是什么?
- 如果前面的陈述是正确的,那么哪个编译器根据 c/c++ 标准生成正确的输出?直觉上,左移没有数字应该给你返回值,即 gcc 输出的内容。
- 如果不是上述情况,为什么没有警告此代码可能由于左移溢出而调用未定义的行为?
如果n
为0,32-n
为32,由于x
有32位,x>>(32-n)
为UB。
链接的 SO post 中的问题不同。这个和signedness没有关系。
您的 n
是 0
,因此执行 x << 32
是 未定义的行为,因为移动 uint32_t
32 位或更多位未定义。
来自 [expr.shift],强调我的:
The behavior is undefined if the right operand
is negative, or greater than or equal to the length in bits of the promoted left operand.
您正在做:
(x >> (32 - n))
与 n == 0
,因此您将 32 位数字右移 32。因此,UB。
部分post没有完全回答。
why are there no warnings that this code may invoke undefined behavior due to left-shift overflow?
查看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是无网编码
编写 C 标准时,某些实现在尝试执行非常大或负数的移位时会表现得很奇怪,例如左移 -1 可能会将 CPU 与中断禁用相关联,而其微代码将值移位 40 亿次,并且长时间禁用中断可能会导致其他系统故障。此外,虽然很少(如果有的话)实现会在精确移动字长时做任何特别奇怪的事情,但实现与返回值不一致。有些人会将其视为零位移位,而另一些人会产生与移位一字长相同的结果,有些人有时会做一个,有时做另一个。
如果标准的作者指定精确移动单词大小可能 select 在这两种可能的行为之间以未指定的方式,那将是有用的,但标准的作者并没有有兴趣指定编译器自然会做的所有事情 有或没有授权 。我认为他们没有考虑普通平台的实现不会自然地产生像上面给出的 "rotate" 这样的表达式的普通行为的想法,并且不想让这些细节使标准混乱。
然而,今天,一些编译器作者认为为 "optimization" 利用所有形式的 UB 比支持有用的自然行为更重要,这些行为以前基本上所有常见的实现都支持。当 y==0 时是否使 "rotate" 表达式出现故障将允许编译器生成一个比其他可能的程序更小的有用程序是无关紧要的。
根据 this interesting paper about undefined behavior optimization in c, the expression (x<<n)|(x>>32-n)
"performs undefined behavior in C when n = 0". This Whosebug 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;
}
使用以下参数编译:-O3 -std=c11 -pedantic -Wall -Wextra
- 在 gcc >5.1.0 中,程序的输出是
10
。 - 在 clang >3.7.0 中输出是
4294967295
.
有趣的是,用 c++ 编译时仍然如此:gcc results, clang results.
因此,我的问题如下:
- 根据我对标准语言的理解,这应该 不 调用未定义/实现定义的行为,因为两个参数都是无符号整数并且 none值为负。这个对吗?如果不是,c11 和 c++11 标准的相关部分是什么?
- 如果前面的陈述是正确的,那么哪个编译器根据 c/c++ 标准生成正确的输出?直觉上,左移没有数字应该给你返回值,即 gcc 输出的内容。
- 如果不是上述情况,为什么没有警告此代码可能由于左移溢出而调用未定义的行为?
如果n
为0,32-n
为32,由于x
有32位,x>>(32-n)
为UB。
链接的 SO post 中的问题不同。这个和signedness没有关系。
您的 n
是 0
,因此执行 x << 32
是 未定义的行为,因为移动 uint32_t
32 位或更多位未定义。
来自 [expr.shift],强调我的:
The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.
您正在做:
(x >> (32 - n))
与 n == 0
,因此您将 32 位数字右移 32。因此,UB。
部分post没有完全回答。
why are there no warnings that this code may invoke undefined behavior due to left-shift overflow?
查看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是无网编码
编写 C 标准时,某些实现在尝试执行非常大或负数的移位时会表现得很奇怪,例如左移 -1 可能会将 CPU 与中断禁用相关联,而其微代码将值移位 40 亿次,并且长时间禁用中断可能会导致其他系统故障。此外,虽然很少(如果有的话)实现会在精确移动字长时做任何特别奇怪的事情,但实现与返回值不一致。有些人会将其视为零位移位,而另一些人会产生与移位一字长相同的结果,有些人有时会做一个,有时做另一个。
如果标准的作者指定精确移动单词大小可能 select 在这两种可能的行为之间以未指定的方式,那将是有用的,但标准的作者并没有有兴趣指定编译器自然会做的所有事情 有或没有授权 。我认为他们没有考虑普通平台的实现不会自然地产生像上面给出的 "rotate" 这样的表达式的普通行为的想法,并且不想让这些细节使标准混乱。
然而,今天,一些编译器作者认为为 "optimization" 利用所有形式的 UB 比支持有用的自然行为更重要,这些行为以前基本上所有常见的实现都支持。当 y==0 时是否使 "rotate" 表达式出现故障将允许编译器生成一个比其他可能的程序更小的有用程序是无关紧要的。