使用按位移位操作进行符号扩展
在此问答后,我试着检查答案,所以我写道:
#include int main () { int t;int i; for (i=120;i> 31; printf ("t = %X , i-128 = %X , ~t & i = %X , ~t = %X \n", t, i-128 , (~t &i), ~t); } return 0; }
输出是:
t = FFFFFFFF , i-128 = FFFFFFF8 , ~t & i = 0 , ~t = 0 t = FFFFFFFF , i-128 = FFFFFFF9 , ~t & i = 0 , ~t = 0 t = FFFFFFFF , i-128 = FFFFFFFA , ~t & i = 0 , ~t = 0 t = FFFFFFFF , i-128 = FFFFFFFB , ~t & i = 0 , ~t = 0 t = FFFFFFFF , i-128 = FFFFFFFC , ~t & i = 0 , ~t = 0 t = FFFFFFFF , i-128 = FFFFFFFD , ~t & i = 0 , ~t = 0 t = FFFFFFFF , i-128 = FFFFFFFE , ~t & i = 0 , ~t = 0 t = FFFFFFFF , i-128 = FFFFFFFF , ~t & i = 0 , ~t = 0 t = 0 , i-128 = 0 , ~t & i = 80 , ~t = FFFFFFFF t = 0 , i-128 = 1 , ~t & i = 81 , ~t = FFFFFFFF t = 0 , i-128 = 2 , ~t & i = 82 , ~t = FFFFFFFF t = 0 , i-128 = 3 , ~t & i = 83 , ~t = FFFFFFFF t = 0 , i-128 = 4 , ~t & i = 84 , ~t = FFFFFFFF t = 0 , i-128 = 5 , ~t & i = 85 , ~t = FFFFFFFF t = 0 , i-128 = 6 , ~t & i = 86 , ~t = FFFFFFFF t = 0 , i-128 = 7 , ~t & i = 87 , ~t = FFFFFFFF t = 0 , i-128 = 8 , ~t & i = 88 , ~t = FFFFFFFF t = 0 , i-128 = 9 , ~t & i = 89 , ~t = FFFFFFFF t = 0 , i-128 = A , ~t & i = 8A , ~t = FFFFFFFF t = 0 , i-128 = B , ~t & i = 8B , ~t = FFFFFFFF
如果t
声明为整数,为什么任何负数的-1 == 0xFFFFFFFF
为-1 == 0xFFFFFFFF
?
From: 右移C中的负数
编辑:根据最新草案标准的 6.5.7节,负数的这种行为取决于实现:
E1 >> E2的结果是E1右移E2位的位置。 如果E1具有无符号类型或者E1具有有符号类型和非负值,则结果的值是E1 / 2 E2的商的整数部分。 如果E1具有带符号类型和负值,则结果值是实现定义的。
并且,您的实现可能正在进行具有两个补码的算术移位
运算符>>
作为有符号右移或算术右移 ,将所有位向右移位指定的次数。 重要的是>>
将最左边的符号位(最高有效位MSB)填充到移位后最左边的位。 这称为符号扩展 ,用于在向右移动时保留负数的符号 。
下面是我的图表表示,其中的示例显示了它是如何工作的(对于一个字节):
例:
i = -5 >> 3; shift bits right three time
五分之二的补码forms是1111 1011
Memory Representation:
MSB +----+----+----+---+---+---+---+---+ | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | +----+----+----+---+---+---+---+---+ 7 6 5 4 3 2 1 0 ^ This seventh, the left most bit is SIGN bit
以下是, >>
如何工作? 当你做-5 >> 3
this 3 bits are shifted out and loss MSB (___________) +----+----+----+---+---+---+---+---+ | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | +----+----+----+---+---+---+---+---+ | \ \ | ------------| ----------| | | | ▼ ▼ ▼ +----+----+----+---+---+---+---+---+ | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | +----+----+----+---+---+---+---+---+ (______________) The sign is propagated
注意:最左边的三个位是1,因为每个移位符号位都被保留,每个位也是正确的。 我写过这个符号是传播的,因为所有这三个位都是因为符号(而不是数据)。
[回答]
在你的输出中
前八行
~t is 0 ==> t is FFFFFFFF ==> t is -1
(注意: -1的2的补码是FFFFFFFF
,因为1 = 00000001
1的补码是FFFFFFFE
,2的补码= 1的补码+1是: FFFFFFFE
+ 00000001
= FFFFFFFF
)
所以t
总是在循环中的前8次中被评估为-1
。 是的 ,怎么样?
在for循环中
for (i=120;i<140;i++){ t = (i - 128) >> 31;
前八次的i
值是i = 120, 121, 122, 123, 124, 125, 126 ,127
所有八个值都小于128 。 因此返回(i - 128) = -8, -7, -6, -5, -4, -3, -2, -1
。 因此在前八次表达式t = (i - 128) >> 31
shift-rights为负数。
t = (i - 128) >> 31 t = -ve number >> 31
因为在你的系统中int是4字节= 32位,所以最右边31
位是移出和丢失,并且由于符号位的传播,对于负数, 1
的所有位值变为1
。 (正如我在上图中显示的一个字节)
所以对于拳头八次:
t = -ve number >> 31 == -1 t = -1 and this gives ~t = 0
因此,对于~t,第八次输出为0。
剩余的最后一行
~t is FFFFFFFF ==> ~t is -1 ==> t is 0
对于剩余的最后一行,在for循环中
for (i=120;i<140;i++){ t = (i - 128) >> 31;
i值为128, 129, 130, 132, 133, 134, 135, 136, 137, 138, 139,
均大于或等于 128.符号位为0
。
因此(i-128)对于剩余的最后一行是>=0
并且对于所有这个MSB符号位= 0
。 并且因为再次向右移动31次除了所有位除了sigh位移出并且符号位0
传播并且用0
填充所有位并且幅度变为0
。
我认为如果我也为一个正数写一个例子会很好。 所以我们举5 >> 3
例子5 >> 3
和5是一个字节是0000 0101
this 3 bits are shifted out and loss MSB (___________) +----+----+----+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | +----+----+----+---+---+---+---+---+ | \ \ | ------------| ----------| | | | ▼ ▼ ▼ +----+----+----+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +----+----+----+---+---+---+---+---+ (______________) The sign is propagated
再次看到我写的符号是传播的 ,所以最左边的三个零是由于符号位。
所以这就是operator >>
Signed right shift do,并保留左操作数的符号 。
为什么t =(i-128)>> 31为每个数字给出零或-1?
当非负32位整数向右移位31个位置时,所有非零位都会移出,最高有效位将被0填充,因此最终得到0。
通常,当负32位整数向右移位31个位置时,最高有效位不会被0填充,而是将它们设置为数字的符号,因此符号传播到所有位并以2的补码表示所有设置为1的位数为-1。 净效果就好像你反复将数字除以2,但稍微扭曲……结果向-infinity舍入而不是朝向0.舍入。例如-2>>1==-1
但-3>>1==-2
和-5>>1==-3
。 这称为算术右移 。
当我说“通常”时,我的意思是C标准允许几种不同的行为用于负值的右移。 最重要的是,它允许有符号整数的非2的补码表示。 但是,通常情况下,您有2的补码表示以及我在上面显示/解释的行为。
因为0或-1,~t也总是-1或0。
这是由于(实现定义的)行为或(i - 128) >> 31
,它基本上复制了(i-128)的最高位[假设32位整数]。 如果i
> 128,则会导致顶部位为零。 如果i
小于128,则结果为负,因此设置了最高位。
由于~t
是“与t
相反的所有位”,如果t
为零,则可以预期t
总是为0xffffffff。
>>
运算符右移是大多数编译器中的算术右移,意味着除以2。
因此,如果例如int i ==-4
(0xfffffffc),那么i>>1 == -2
(0xfffffffe)。
说完这个,我建议你检查代码的汇编。
例如,x86有2个单独的指令 – shr
& sar
,分别表示逻辑移位和算术移位。
通常,编译器对无符号变量使用shr
(逻辑移位),对有符号变量使用sar
(算术移位)。
下面是使用gcc -S
生成的C代码和相应的程序集:
AC:
int x=10; unsigned int y=10; int main(){ unsigned int z=(x>>1)+(y>>1); return 0; }
如:
.file "ac" .globl x .data .align 4 .type x, @object .size x, 4 x: .long 10 .globl y .align 4 .type y, @object .size y, 4 y: .long 10 .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $16, %esp movl x, %eax sarl %eax ; <~~~~~~~~~~~~~~~~ Arithmetic shift, for signed int movl y, %edx shrl %edx ; <~~~~~~~~~~~~~~~~ Logical shift, for unsigned int addl %edx, %eax movl %eax, -4(%ebp) movl $0, %eax leave ret .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2" .section .note.GNU-stack,"",@progbits
C和C ++中的规则是负值右移的结果是实现定义的。 请阅读编译器的文档。 您获得的各种解释都是有效的方法,但这些解释都不是语言定义所强制要求的。