用shift移出符号位

时间:2016-07-21 14:53:23

标签: c language-lawyer bit-shift

总是定义行为以这种方式提取32位整数的符号:

#include <stdint.h>

int get_sign(int32_t x) {
    return (x & 0x80000000) >> 31;
}

我是否总是得到01的结果?

5 个答案:

答案 0 :(得分:4)

不,这样做是不正确的,因为正确移位带有负值的有符号整数是实现定义的,如C标准中所规定的那样:

  

6.5.7按位移位运算符

     

E1 >> E2的结果是E1右移E2位位置。如果E1具有无符号类型或E1具有有符号类型和非负值,则结果的值是E1 / 2 {{1的商的不可分割部分}} 。如果E2具有签名类型和负值,则结果值是实现定义的。

您应该在屏蔽和转移之前将E1转换为x

编辑:错误的答案!我将这里的答案作为一个好看,直观但不正确的推理的例子。正如其他答案中所解释的那样,在发布的代码中没有正确移位负值。 (uint32_t)的类型是有符号整数或无符号整数类型之一,具体取决于实现特征,但其值始终为正,x & 0x800000000。右移这个值不是实现定义的,结果总是21474836480。结果是否为符号位的值不太明显:除了一些非常扭曲的极端情况之外,它是符号位的值,混合架构不太可能存在,并且可能无论如何都符合标准。

答案 1 :(得分:2)

由于答案假定固定宽度类型可用,因此负零不存在 1 ,提取符号位的唯一正确方法是简单地检查值是否为阴性:

_Bool Sign( const int32_t a )
{
    return a < 0 ;
}

1 固定宽度类型需要两个补码表示,它不具有负零。

答案 2 :(得分:1)

是的,1s和2s补充体系结构是正确的,但出于以下原因:

  1. 对于绝大多数常见硬件,其中intint32_t的类型相同且unsigneduint32_t相同,常量文字0x80000000的类型为{ {1}}。 unsigned int操作的左操作数转换为&unsigned int的结果具有相同的类型。右移应用于&,值为unsigned int0,没有实现定义的行为。
  2. 在其他平台上,1可能有不同的类型,行为可能是实现定义的:

      如果0x80000000类型的值位数超过31,则
    • 0x80000000可以是int类型。在这种情况下,int会提升为x,其值不会更改。

      1. 如果int使用1s补码或2s补码表示,则符号位将复制到更高有效位。掩码操作的结果为int,值为int0。右移0x80000000位置分别评估为310,也没有实现定义的行为。
      2. 相反,如果1使用符号/幅度表示,保留int的值将有效地重置其第31位,将符号位移到值位之外。掩码操作将评估为x,结果将不正确。
    • 0可以是0x80000000类型,如果long类型的值位数少于31个,或者intINT_MIN == -INT_MAX有更多31个值位。在这种情况下,long会转换为x,其值不会更改,其后果与long相同。对于int的1或2s补码表示,掩码操作评估为longlong的正0值,并将其右移31个位置,并给出0x800000000,对于符号/幅度,在所有情况下结果应为1

    • 0可以是0x80000000类型,如果unsigned long类型的值位数少于31且int具有31个值位且使用2s补码表示。在这种情况下,long会转换为x,保持符号位完整。掩码操作的结果为unsigned longunsigned long的{​​{1}}值,并将其右移31位,并提供00x80000000。< / p>

    • 最后,0可以是1类型,如果0x80000000类型的值位数少于31个,或long longint有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:八进制或十六进制常量:intunsigned,{{1} }或long ....无论其类型如何,它的均为unsigned long

+2,147,483,648类型将是x & 0x80000000类型和int32_t类型中较广泛的类型。如果两种类型的宽度相同且符号不同,则它将是无符号的。 0x80000000INT32_MAX且小于+2,147,483,647,因此+2,147,483,648必须是比0x80000000更宽的类型(或相同且无符号)。因此,无论int32_t类型如何,0x80000000都将是相同的类型。

x & 0x80000000int如何实现为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