为什么这个bit-hack代码可移植?

时间:2012-10-12 09:13:52

标签: c

int v;
int sign; // the sign of v ;
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));

Q1:由于v由int类型定义,所以为什么还要再次将其投入int?它与便携性有关吗?

修改

Q2:

sign = v >> (sizeof(int) * CHAR_BIT - 1); 

此snippt 不是可移植,因为signed int正确移位实现已定义,如何填充左边距比特是由complier.So

 -(int)((unsigned int)((int)v) 

做可口的伎俩。请解释一下为什么有效。 左边距位中的unsigned int alway padding 0 右移

4 个答案:

答案 0 :(得分:4)

它不是严格可移植的,因为理论上int和/或unsigned int可能有填充位。

unsigned int有填充位的假设实现中,向右移sizeof(int)*CHAR_BIT - 1会产生未定义的行为

sizeof(int)*CHAR_BIT - 1 >= WIDTH

但对于unsigned int没有填充位的所有实现 - 据我所知,这意味着所有现有的实现 - 代码

int v;
int sign; // the sign of v ;
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
如果sign

必须将-1设置为v < 0,如果v >= 0,则设置为0。 (注意 - 感谢Sander De Dycker指出 - 如果int的负数为零,那么sign = 0也会产生-0 == 0。如果实现支持负零负零的符号应为-1,这种移位和比较v < 0都不会产生这种情况,需要直接检查对象表示。)

在转变之前投射到int之前的演员是完全多余的并且什么都不做。

它是 - 忽略假设的填充位问题 - 可移植,因为转换为无符号整数类型和无符号整数类型的表示由标准规定。

转换为无符号整数类型是简化模unsigned int,其中2^WIDTH是类型中的值位数,因此结果位于0到WIDTH的范围内

由于2^WIDTH - 1中没有填充位,unsigned int范围的大小不能大于int的大小,因此标记的整数的标准命令(6.2.6.2)将被表示在其中一个

  • 符号和幅度
  • 些'补充
  • 二的补充

可表示的最小unsigned int值为int。因此,值为-2^(WIDTH-1)的{​​{1}}的{​​{1}}转换为int,因此设置了最高位。

另一方面,非负-k值不能大于2^WIDTH - k >= 2^(WIDTH-1),因此转换将保留其值,并且不会设置最高有效位。

因此,当转换结果向右移int位时(同样,我们假设2^(WIDTH-1) - 1中没有填充位,因此WIDTH - 1),它将产生0如果unsigned int值为非负值,则为WIDTH == sizeof(int)*CHAR_BIT,如果值为负值。

答案 1 :(得分:1)

不过它的过度施法。没有必要将它强制转换为int。然而,这并没有伤害。

编辑:值得注意的是它可以这样做,所以v的类型可以改为其他东西,或者它可能曾经是另一种数据类型,并且在它被转换为int之后,演员表从未被删除。 / p>

答案 2 :(得分:1)

它应该是非常便携的,因为当您将int转换为unsigned int(通过强制转换)时,您会收到一个值,该值是原始int值的2的补码位表示,最重要的位是符号位。

更新:更详细的说明......

我假设intunsigned int中没有填充位,并且两种类型中的所有位都用于表示整数值。这是对现代硬件的合理假设。填充位已经成为过去,我们仍然在当前和最近的C标准中为了向后兼容性(即能够在旧机器上运行代码)来携带它们。

有了这个假设,如果intunsigned int中有NN = CHAR_BIT * sizeof(int),那么根据C标准,我们有3个用于表示int的选项,这是一种签名类型:

  1. 符号和幅度表示,允许从 - (2 N-1 -1)到2 N-1 -1
  2. 的值
  3. 一个补码表示,也允许从 - (2 N-1 -1)到2 N-1 -1
  4. 的值
  5. 二进制补码表示,允许值从-2 N-1 到2 N-1 -1或者可能来自 - (2 N-1 -1)至2 N-1 -1
  6. 符号和幅度以及一个补码表示也是过去的事情,但我们暂时不要抛弃它们。

    当我们将int转换为unsigned int时,规则是非负值v(&gt; = 0)不会更改,而负值{{1 }(&lt; 0)更改为2 N + v的正值,因此v = (unsigned int)-1

    因此,非负UINT_MAX的{​​{1}}将始终在0到2 N-1 -1的范围内,并且{{1}的最高位1}}将为0。

    现在,对于从-2到 N-1 到-1的范围内的负(unsigned int)v(此范围是{的三个可能表示的负范围的超集。 {1}}),v将在2 N +( - 2 N-1 )至2 N +( - 1),简化了我们到达2 N-1 到2 N -1的范围。显然,该值的最重要部分将始终为1.

    如果仔细查看所有这些数学运算,您会看到(unsigned int)v的值与二进制补码表示中的v的二进制文件完全相同:

    ...
    int = -2:(unsigned int)v = 2 N - 2 = 111 ... 110 2
    (unsigned)v = -1:v = 2 N - 1 = 111 ... 111 2
    v = 0:(unsigned)v = 0 = 000 ... 000 2
    v = 1:(unsigned)v = 1 = 000 ... 001 2
    ...

    那么,对于v&gt; = 0,值(unsigned)v的最高有效位将为0,对于v <0,将为1。

    现在,让我们回到符号和幅度以及一个补码表示。这两个表示可能允许两个零,(unsigned)v(unsigned)v。但算术计算并没有明显地区分vv,它仍然是+0,无论你是添加它,减去它,乘以它还是比较它。作为观察员,您通常不会看到-0+0或与其中一个有任何区别。

    尝试观察和区分-00通常是毫无意义的,如果您想使代码可移植,通常不应期望或依赖于存在两个零。

    +0不会告诉您-0+0之间的区别,在这两种情况下,-0都相当于(unsigned int)v

    因此,使用此方法,您将无法判断内部v=+0v=-0还是(unsigned int)v,您不会以{{}}的方式提取v的符号位{1}}。

    但是,再次说明,通过区分两个零来获得实际价值,并且您不希望在便携式代码中区分这种区别。

    所以,有了这个,我敢于在实践中声明问题中提出的符号提取方法非常/非常/非常多/等等。

    但这种方法是一种矫枉过正的方法。原始代码中的0u是不必要的,因为v已经是-0

    这应该足够容易理解:

    +0

答案 3 :(得分:0)

不是。标准没有定义整数的表示,因此不可能准确地保证其结果的可移植性。获得整数符号的唯一方法是进行比较。