使用按位移位操作对扩展进行签名

Sign extension with bitwise shift operation

本文关键字:扩展 操作 作对      更新时间:2023-10-16

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

#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任何负数t都会-1 == 0xFFFFFFFF

发件人:C 中的右移负数

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

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

而且,您的实现可能正在对 2 的补码数进行算术移位


运算符>>有符号右移位或算术右移位,将所有位向右移动指定的次数。重要的是>>在移位后将最左边的符号位(最高有效位 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

注意:最左边的三个位是一个,因为每个移位符号位都被保留,每个位也是正确的。我已经写了符号被传播,因为这三个位都是因为符号(但不是数据(。

[答案]
在您的输出中

前八行

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

(注:-1的2补码是FFFFFFFF的,因为1 = 00000001,1的补码是FFFFFFFE的,而2的补码=1的补码+1即:FFFFFFFE+00000001=FFFFFFFF(

因此,t总是在循环的前八次中-1评估。是的,如何?

在 for 循环中

for (i=120;i<140;i++){
     t = (i - 128) >> 31;

前八次的ii = 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

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

因此,这就是运算符>> Signed 右移所做的,并保留左操作数的符号

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

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

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

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

Becaue t要么是 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个独立的指令 - shrsar,分别表示逻辑移位和算术移位。
通常,编译器对无符号变量使用shr(逻辑移位(和对有符号变量使用sar(算术移位(。


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

交流:

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

A.S:

    .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

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