C语言中右移的奇怪行为(有时是算术的,有时是逻辑的)

C语言中右移的奇怪行为(有时是算术的,有时是逻辑的),c,C,GCC版本5.4.0 Ubuntu 16.04 我注意到当我将值存储在变量中或不存储时,C中右移的一些奇怪行为 此代码段正在打印预期行为0xf0000000 int main() { int x = 0x80000000 printf("%x", x >> 3); } 以下两个代码段正在打印0x10000000,在我看来这很奇怪,它在负数上执行逻辑移位 一, 二, 任何见解都将不胜感激。我不知道这是否是我的个人计算机的一个特定问题,在这种情况下它不能被复制,或者它是

GCC版本5.4.0 Ubuntu 16.04

我注意到当我将值存储在变量中或不存储时,C中右移的一些奇怪行为

此代码段正在打印预期行为
0xf0000000

int main() {
    int x = 0x80000000
    printf("%x", x >> 3);
}
以下两个代码段正在打印
0x10000000
,在我看来这很奇怪,它在负数上执行逻辑移位

一,

二,


任何见解都将不胜感激。我不知道这是否是我的个人计算机的一个特定问题,在这种情况下它不能被复制,或者它是否只是C中的一个行为。

负整数的右移有实现定义的行为。所以当右移负数时,你不能“期待”任何事情

因此,它与您的实现中的情况一样。这并不奇怪

6.5.7/5[…]如果E1有符号类型和负值,则结果值由实现定义

它还可以调用UB

6.5.7/4[…]如果E1具有有符号类型和非负值,且E1×2E2可在结果类型中表示,则为结果类型 价值否则,行为是未定义的


正如@p_uuj_uu所指出的,右移取决于实现,因此您不应该依赖它在不同平台上保持一致

至于您的特定测试,它在单个平台上(可能是32位Intel或其他使用两个补码32位整数表示的平台),但仍显示不同的行为:


GCC使用可用的最高精度(通常为64位,但可能更高)对文字常量执行操作。现在,语句
x=0x8000000>>3
不会被编译成在运行时右移的代码,而是编译器计算出两个操作数都是常量,并将它们折叠成
x=0x10000000
。对于GCC,文字0x8000000不是一个负数。它是正整数2^31

另一方面,
x=0x8000000
将值2^31存储到x中,但32位存储器不能将其表示为您作为整数文本给出的正整数2^31-该值超出了由32位2的补码带符号整数表示的范围。高阶位以符号位结束-因此这在技术上是一个溢出,尽管没有得到警告或错误。然后,当您使用
x>>3
时,操作现在在运行时执行(不是由编译器执行),使用32位算术-它将其视为负数。

引用from,表示没有任何后缀的十六进制整数常量

整型常量的类型是值可以适应的第一种类型,它来自类型列表,取决于使用了哪个数字基和哪个整型后缀

int unsigned int long int unsigned long int long long int(since C99) unsigned long long int(since C99) 以实现定义的方式将
unsigned int
转换为
int
,但语句

int x = 0x80000000;
int x = 0x80000000 >> 3;
无符号int
转换为
int
之前,将其右移到
无符号int
,因此您看到的结果不同

编辑


另外,如前所述,格式说明符
%x
需要一个无符号整数参数,而传递
int
会导致未定义的行为。

在具有32位整数的系统上,
0x8000000
是一个无符号整数,因为它不能表示为32位有符号整数而不变为负数。因此,移位是无符号的。执行移位后,将结果赋给有符号整数不会影响结果。@TomKarzes
0x8000000
在所有系统上都是正数;因此
0x8000000>>3
始终是值0x10000000,无论它是有符号的还是无符号的
printf(“%x”,x>>3)
使用错误的
int
@tomkarze格式规范导致未定义行为,该规范仅适用于C99之前具有32位整数的系统。现代编译器不会有这种行为,对于32位整数,
0x8000000
是无符号整数,因此移位是无符号的。您显示的引号不适用于这种情况。@TomKarzes,但适用于变量的类型。@TomKarzes int x=0x8000000 32位整数在分配给
int
变量时是有符号的。我想你是错的。你是对的,第一种情况在移位之前在一个有符号的32位变量中有值,所以你的评论确实适用于这种情况。我指的是OP询问的另外两种情况,移位直接在常数上执行,导致无符号移位。@TomKarzes,OP不能从他的第一个例子中得到任何东西。对于32位整数,0x8000000是无符号整数,所以移位是无符号的。@TomKarzes:实际上,
0x8000000
不是32位或64位(或任何其他大小)-它是编译器可以随意处理的文本常量。顺便说一句:根据标准,它是一个有符号整数-如果你想告诉编译器将其视为无符号,你必须执行
0x80000000U
。(它也被移位为一个有符号整数-简单地说,编译器在内部用64位(或更高)的数学进行了移位,所以它的结果是0x10000000,正如预期的那样)。@LeoK(给定
sizeof(int)=4&&CHAR\u位==8
)。@LeoK我相信它的定义比这更严格,但规则很复杂。我手头没有标准的副本,但是根据,对于十六进制常量,类型是值将适合的第一种类型,其中类型是
int
unsigned int
long
unsigned long
long
unsigned long
。因此,对于32位整数大小,
0x8000000
具有类型
unsigned int
“GCC使用可用的最高精度对文字常量执行操作”不正确或不相关;gcc遵循以下原则:
int x = 0x80000000;
int x = 0x80000000 >> 3;