它应该非常便携,因为当您转换int
为unsigned int
(通过强制转换)时,您会收到一个值,该值是原始值的 2 的补码位表示,int
最高有效位是符号位。
更新:更详细的解释......
我假设其中没有填充位,int
并且unsigned int
这两种类型中的所有位都用于表示整数值。对于现代硬件来说,这是一个合理的假设。填充位已成为过去,为了向后兼容(即能够在旧机器上运行代码),我们仍然在当前和最近的 C 标准中携带它们。
有了这个假设,如果int
和unsigned int
有N
位(N
= CHAR_BIT * sizeof(int)
),那么根据 C 标准,我们有 3 个选项来表示int
,这是一个有符号类型:
- 符号和大小表示,允许值从 -(2 N-1 -1) 到 2 N-1 -1
- 一个的补码表示,也允许从 -(2 N-1 -1) 到 2 N-1 -1的值
- 二进制补码表示,允许值从 -2 N-1到 2 N-1 -1 或可能从 -(2 N-1 -1) 到 2 N-1 -1
符号和大小和补码表示也已成为过去,但我们暂时不要将它们扔掉。
当我们转换int
为unsigned int
时,规则是非负值v
(>=0) 不变,而负值(<0) 变为 2 N +v
的正值,因此= 。v
(unsigned int)-1
UINT_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
。但是算术计算并没有明显区分+0
and -0
,它仍然是 a 0
,无论你加,减,乘还是比较它。你,作为一个观察者,通常不会看到+0
或-0
与拥有一个或另一个有任何区别。
试图观察和区分+0
通常-0
是没有意义的,如果你想让你的代码可移植,你通常不应该期望或依赖两个零的存在。
(unsigned int)v
不会告诉你 和 之间的区别v=+0
,v=-0
在这两种情况下(unsigned int)v
都等同于0u
。
因此,使用这种方法,您将无法判断内部v
是 a-0
还是 a +0
,您不会以这种方式提取 v 的符号位v=-0
。
但是同样,您从区分两个零中没有任何实际价值,并且您不希望在可移植代码中进行这种区分。
因此,我敢于声明问题中提出的符号提取方法在实践中非常/非常/相当/等可移植。
不过,这种方法有点矫枉过正。并且(int)v
在原始代码中是不必要的,因为v
已经是int
.
这应该绰绰有余并且易于理解:
int sign = -(v < 0);