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

Q1:既然 v in 是由 type of 定义的int,那为什么还要再把它转换成int呢?跟便携有关系吗?

编辑:

Q2:

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

这个片段 不可移植,因为右移实现定义signed int的,如何填充左边距位取决于编译器。所以

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

做便携的把戏。请解释一下为什么这有效。不是总是在左边距位中填充0unsigned int

4

4 回答 4

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-1ifv < 0和 0 if v >= 0。(注意——感谢Sander De Dycker指出——如果有一个负零,int那也会产生会产生这种情况,需要直接检查对象表示。)sign = 0-0 == 0-1v < 0

转换之前转换intunsigned int转换之前完全是多余的并且什么都不做。

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

转换为无符号整数类型是减少模数2^WIDTH,其中WIDTH是类型中值的位数,因此结果位于 0 到 0 到 0 的范围内2^WIDTH - 1

由于没有填充位,unsigned int范围的大小int不能大于 的unsigned int,并且有符号整数的标准要求(6.2.6.2)用以下之一表示

  • 符号和大小
  • 一个的补码
  • 二进制补码

最小可能的可表示int值是-2^(WIDTH-1)int因此,负值-k被转换为2^WIDTH - k >= 2^(WIDTH-1)并因此设置了最高有效位。

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

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

于 2012-10-12T09:43:30.577 回答
1

不,它只是过度铸造。无需将其转换为 int。不过不痛。

编辑:值得注意的是,它可能会这样做,因此 v 的类型可以更改为其他类型,或者它可能曾经是另一种数据类型,并且在转换为 int 之后,从未删除过强制转换。

于 2012-10-12T09:17:22.923 回答
1

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

更新:更详细的解释......

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

有了这个假设,如果intunsigned intN位(N= CHAR_BIT * sizeof(int)),那么根据 C 标准,我们有 3 个选项来表示int,这是一个有符号类型:

  1. 符号和大小表示,允许值从 -(2 N-1 -1) 到 2 N-1 -1
  2. 一个的补码表示,也允许从 -(2 N-1 -1) 到 2 N-1 -1的值
  3. 二进制补码表示,允许值从 -2 N-1到 2 N-1 -1 或可能从 -(2 N-1 -1) 到 2 N-1 -1

符号和大小和补码表示也已成为过去,但我们暂时不要将它们扔掉。

当我们转换intunsigned int时,规则是非负值v(>=0) 不变,而负值(<0) 变为 2 N +v的正值,因此= 。v(unsigned int)-1UINT_MAX

因此,(unsigned int)v对于非负数v将始终在 0 到 2 N-1 -1 的范围内,并且最高有效位(unsigned int)v将为 0。

现在,对于v从 到 -2 N-1到 -1 范围内的负数(该范围是 的三种可能表示形式的负数范围的超集int),(unsigned int)v将在 2 N +(-2 N- 1 ) 到 2 N +(-1),简化我们得到从 2 N-1到 2 N -1 的范围。显然,该值的最高有效位将始终为 1。

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

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

因此,该值的最高有效位(unsigned)v将为v>=0 的 0 和v<0 的 1。

现在,让我们回到符号和大小和一个的补码表示。这两种表示可以允许两个零, a+0和 a -0。但是算术计算并没有明显区分+0and -0,它仍然是 a 0,无论你加,减,乘还是比较它。你,作为一个观察者,通常不会看到+0-0与拥有一个或另一个有任何区别。

试图观察和区分+0通常-0是没有意义的,如果你想让你的代码可移植,你通常不应该期望或依赖两个零的存在。

(unsigned int)v不会告诉你 和 之间的区别v=+0v=-0在这两种情况下(unsigned int)v都等同于0u

因此,使用这种方法,您将无法判断内部v是 a-0还是 a +0,您不会以这种方式提取 v 的符号位v=-0

但是同样,您从区分两个零中没有任何实际价值,并且您不希望在可移植代码中进行这种区分。

因此,我敢于声明问题中提出的符号提取方法在实践中非常/非常/相当/等可移植。

不过,这种方法有点矫枉过正。并且(int)v在原始代码中是不必要的,因为v已经是int.

这应该绰绰有余并且易于理解:

int sign = -(v < 0);
于 2012-10-12T09:27:32.067 回答
0

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

于 2012-10-12T09:21:51.840 回答