6

这个问答之后,我试图检查答案,所以我写道:

#include <stdio.h>

int main ()
{

        int t;int i;
        for (i=120;i<140;i++){
                t = (i - 128) >> 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 == 0xFFFFFFFFt

4

5 回答 5

5

为什么 t = (i - 128) >> 31 为每个数字给出零或-1?

当一个非负 32 位整数右移 31 个位置时,所有非零位都被移出,最高有效位被 0 填充,所以你最终得到 0。

通常,当一个负的 32 位整数右移 31 个位置时,最高有效位不会用 0 填充,而是设置为数字的符号,因此符号会传播到所有位和 2 的补码表示所有设置为 1 的位等于 -1。最终效果就像您反复将数字除以 2,但稍有扭曲......结果向 -infinity 而不是向 0 四舍五入。例如-2>>1==-1but-3>>1==-2-5>>1==-3。这称为算术右移

当我说“通常”时,我的意思是 C 标准允许负值右移的几种不同行为。最重要的是,它允许有符号整数的非 2 补码表示。然而,通常你有 2 的补码表示和我在上面展示/解释的行为。

于 2013-03-31T13:48:18.757 回答
5

来自:在 C 中右移负数

编辑: 根据最新草案标准的第 6.5.7 节,负数的这种行为取决于实现:

E1 >> E2 的结果是 E1 右移 E2 位位置。如果 E1 具有无符号类型或 E1 具有有符号类型和非负值,则结果的值是 E1 / 2 E2的商的整数部分。如果 E1 具有带符号类型和负值,则结果值是实现定义的。

而且,您的实现可能正在使用二进制补码数进行算术移位


运算符>>作为有 符号右移算术右移,将所有位右移指定次数。重要的是>> 在移位后将最左边的符号位(Most Significant Bit MSB)填充到最左边的位。这称为符号扩展,用于在您向右移动负数时 保留负数的符号。

下面是我的图解表示,并带有一个示例来说明它是如何工作的(一个字节):
示例:

i = -5 >> 3;  shift bits right three time 

五的补码形式是1111 1011 内存表示:

 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

注意:最左边的三位是一位,因为在每个移位符号位都被保留并且每个位也是正确的。我写了符号被传播,因为所有这三位都是因为符号(而不是数据)。

[ANSWER]
在你的输出中

前八行

      ~t is 0
==>    t is FFFFFFFF 
==>    t is -1

(注意:-1 的 2 的补码是FFFFFFFF,因为1 = 000000011 的 1 的补码是FFFFFFFE,并且 2 的补码 = 1 的补码 + 1 即:FFFFFFFE+ 00000001= FFFFFFFF

所以t总是-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右移一个负数。

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 次,除了然后叹息位移出和符号位0传播并填充所有位,0幅度变为0.

我认为如果我也写一个正数的例子会很好。所以我们举个例子 5 >> 3五是一字节是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

再看看我写的符号是传播的,所以最左边的三个零是由于符号位。

所以这就是运算符有>> 符号右移所做的,并保留左操作数的符号。

于 2013-03-31T15:00:27.543 回答
2

因为t是 0 或 -1,所以 ~t 也总是 -1 或 0。

这是由于(实现定义的)行为 or (i - 128) >> 31,它本质上复制了 (i-128) 的最高位 [假设 32 位整数]。如果i大于 128,它将导致最高位为零。如果i小于 128,则结果为负,因此设置了最高位。

由于~t是“与”相反的所有位,因此如果为零t,您可以期望它t始终为 0xffffffff 。t

于 2013-03-31T13:29:06.427 回答
1

>>运算符右移在大多数编译器中是算术右移,意思是除以 2 。

所以,如果,例如int i ==-4(0xfffffffc),那么i>>1 == -2(0xffffffffe)。

话虽如此,我建议您检查代码的汇编。
例如 x86 有 2 条单独的指令 - shr& sar,分别表示逻辑移位和算术移位。
通常,编译器对无符号变量使用shr(逻辑移位),对有符号变量使用sar(算术移位)。


下面是生成的 C 代码和相应的程序集gcc -S

交流:

int x=10;
unsigned int y=10;

int main(){
    unsigned int z=(x>>1)+(y>>1);
    return 0;
}

作为:

    .file   "a.c"
.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
于 2013-03-31T14:30:41.210 回答
0

C 和 C++ 中的规则是负值右移的结果是实现定义的。因此,请阅读编译器的文档。您得到的各种解释都是有效的方法,但语言定义没有强制要求这些方法。

于 2013-03-31T19:43:54.893 回答