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 右移?
答案 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的补码位表示,最重要的位是符号位。
更新:更详细的说明......
我假设int
和unsigned int
中没有填充位,并且两种类型中的所有位都用于表示整数值。这是对现代硬件的合理假设。填充位已经成为过去,我们仍然在当前和最近的C标准中为了向后兼容性(即能够在旧机器上运行代码)来携带它们。
有了这个假设,如果int
和unsigned int
中有N
位N
= CHAR_BIT * sizeof(int)
,那么根据C标准,我们有3个用于表示int
的选项,这是一种签名类型:
符号和幅度以及一个补码表示也是过去的事情,但我们暂时不要抛弃它们。
当我们将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
。但算术计算并没有明显地区分v
和v
,它仍然是+0
,无论你是添加它,减去它,乘以它还是比较它。作为观察员,您通常不会看到-0
或+0
或与其中一个有任何区别。
尝试观察和区分-0
和0
通常是毫无意义的,如果您想使代码可移植,通常不应期望或依赖于存在两个零。
+0
不会告诉您-0
和+0
之间的区别,在这两种情况下,-0
都相当于(unsigned int)v
。
因此,使用此方法,您将无法判断内部v=+0
是v=-0
还是(unsigned int)v
,您不会以{{}}的方式提取v的符号位{1}}。
但是,再次说明,通过区分两个零来获得实际价值,并且您不希望在便携式代码中区分这种区别。
所以,有了这个,我敢于在实践中声明问题中提出的符号提取方法非常/非常/非常多/等等。
但这种方法是一种矫枉过正的方法。原始代码中的0u
是不必要的,因为v
已经是-0
。
这应该足够容易理解:
+0
答案 3 :(得分:0)
不是。标准没有定义整数的表示,因此不可能准确地保证其结果的可移植性。获得整数符号的唯一方法是进行比较。