总是定义行为以这种方式提取32位整数的符号:
#include <stdint.h>
int get_sign(int32_t x) {
return (x & 0x80000000) >> 31;
}
我是否总是得到0
或1
的结果?
答案 0 :(得分:4)
不,这样做是不正确的,因为正确移位带有负值的有符号整数是实现定义的,如C标准中所规定的那样:
6.5.7按位移位运算符
E1 >> E2
的结果是E1
右移E2
位位置。如果E1
具有无符号类型或E1
具有有符号类型和非负值,则结果的值是E1 / 2
{{1的商的不可分割部分}} 。如果E2
具有签名类型和负值,则结果值是实现定义的。
您应该在屏蔽和转移之前将E1
转换为x
。
编辑:错误的答案!我将这里的答案作为一个好看,直观但不正确的推理的例子。正如其他答案中所解释的那样,在发布的代码中没有正确移位负值。 (uint32_t)
的类型是有符号整数或无符号整数类型之一,具体取决于实现特征,但其值始终为正,x & 0x80000000
或0
。右移这个值不是实现定义的,结果总是2147483648
或0
。结果是否为符号位的值不太明显:除了一些非常扭曲的极端情况之外,它是符号位的值,混合架构不太可能存在,并且可能无论如何都符合标准。
答案 1 :(得分:2)
由于答案假定固定宽度类型可用,因此负零不存在 1 ,提取符号位的唯一正确方法是简单地检查值是否为阴性:
_Bool Sign( const int32_t a )
{
return a < 0 ;
}
1 固定宽度类型需要两个补码表示,它不具有负零。
答案 2 :(得分:1)
是的,1s和2s补充体系结构是正确的,但出于以下原因:
int
与int32_t
的类型相同且unsigned
与uint32_t
相同,常量文字0x80000000
的类型为{ {1}}。 unsigned int
操作的左操作数转换为&
,unsigned int
的结果具有相同的类型。右移应用于&
,值为unsigned int
或0
,没有实现定义的行为。在其他平台上,1
可能有不同的类型,行为可能是实现定义的:
0x80000000
类型的值位数超过31,则 0x80000000
可以是int
类型。在这种情况下,int
会提升为x
,其值不会更改。
int
使用1s补码或2s补码表示,则符号位将复制到更高有效位。掩码操作的结果为int
,值为int
或0
。右移0x80000000
位置分别评估为31
和0
,也没有实现定义的行为。1
使用符号/幅度表示,保留int
的值将有效地重置其第31位,将符号位移到值位之外。掩码操作将评估为x
,结果将不正确。 0
可以是0x80000000
类型,如果long
类型的值位数少于31个,或者int
和INT_MIN == -INT_MAX
有更多31个值位。在这种情况下,long
会转换为x
,其值不会更改,其后果与long
相同。对于int
的1或2s补码表示,掩码操作评估为long
或long
的正0
值,并将其右移31个位置,并给出0x80000000
或0
,对于符号/幅度,在所有情况下结果应为1
。
0
可以是0x80000000
类型,如果unsigned long
类型的值位数少于31且int
具有31个值位且使用2s补码表示。在这种情况下,long
会转换为x
,保持符号位完整。掩码操作的结果为unsigned long
或unsigned long
的{{1}}值,并将其右移31位,并提供0
或0x80000000
。< / p>
最后,0
可以是1
类型,如果0x80000000
类型的值位数少于31个,或long long
和int
有31个值位但不使用2s补码表示。在这种情况下,INT_MIN == -INT_MAX
会转换为long
,保持其值,与x
情况相同,如果long long
表示符号/幅度。
这个问题是故意设计的。答案是只要平台不使用符号/幅度表示,您就会得到正确的结果。但C标准坚持支持2s补码以外的整数表示,但后果非常明显。
编辑:仔细阅读C标准的 6.2.6.2整数类型部分似乎排除了在同一实现中共存的有符号整数类型的不同表示的可能性。这使得代码完全定义为已发布,因为类型int
的存在意味着所有有符号整数类型的2s补码表示。
答案 3 :(得分:1)
我总是得到0或1的结果吗?
是
简单回答:
0x80000000 >> 31
始终 1.
0x00000000 >> 31
总是 0。
见下文。
[编辑]
是否始终定义行为以这种方式提取32位整数的符号
是的,除了一个角落案件。
0x80000000
应该实现为int/long
(这意味着类型&gt; 32位)和有符号整数类型是带符号幅度(或者可能是一个补码) a novel machine,然后将int32_t x
转换为int/long
会将符号位移动到新的位位置,从而使& 0x80000000
无实际意义。
如果C支持int32_t
(必须是2的补码)和任何int/long/long long
作为非2的补码,则问题是开放的。
0x80000000
是十六进制常量。 “整数常量的类型是相应列表中可以表示其值的第一个”C11§6.4.4.15:八进制或十六进制常量:int
,unsigned
,{{1} }或long
....无论其类型如何,它的值均为unsigned long
。
+2,147,483,648
的类型将是x & 0x80000000
类型和int32_t
类型中较广泛的类型。如果两种类型的宽度相同且符号不同,则它将是无符号的。 0x80000000
为INT32_MAX
且小于+2,147,483,647
,因此+2,147,483,648
必须是比0x80000000
更宽的类型(或相同且无符号)。因此,无论int32_t
类型如何,0x80000000
都将是相同的类型。
x & 0x80000000
和int
如何实现为2的补码没有区别。
long
操作不会更改&
值的符号,因为它是无符号整数类型或符号位处于更重要的位置。 0x80000000
然后具有x & 0x80000000
或+2,147,483,648
的值。
无论整数类型如何,都可以很好地定义正数的右移。负值的右移是实现定义的。见C11§6.5.75。0
绝不是负数。
因此x & 0x80000000
定义明确,0或1。
(x & 0x80000000) >> 31
(不是每个帖子标题“用移位提取符号位”)是可以理解的,对于我能想到的大多数实例来说,它当然是首选代码。这两种方法都不会产生任何可执行代码差异。
答案 4 :(得分:0)
此表达式是否具有精确定义的语义,它不是获取符号位的最可读方式。这是更简单的选择:
int get_sign(int32_t x) {
return x < 0;
}
正如2501正确指出的那样,int32_t
被定义为具有2s补码表示,因此与0
相比具有与提取最高有效位相同的语义。
顺便提一下,两个函数都使用gcc 5.3
编译为完全相同的代码:
get_sign(int):
movl %edi, %eax
shrl $31, %eax
ret