位运算:有符号整数右移和无符号整数右移的区别

如果我们定义一个符号整数 int a = 0x80000000; 然后执行 a = a >> 1; 那么a将变为0xc0000000;

我们再定义一个符号整数 unsigned int b = 0x80000000; 然后执行 b = b >> 1; 那么b则将变为0x40000000;

为什么有这样的差别呢? 先写一小段代码,看看右移的演变过程:

 1 #include <stdio.h>
 2 
 3 int
 4 main(int argc, char *argv[])
 5 {
 6              int a = 0x80000000;
 7     unsigned int b = 0x80000000;
 8 
 9     (void) printf("|%2s| %10s | %10s\n", "ID", "int", "unsigned int");
10     (void) printf("|%2d| 0x%08x | 0x%08x\n", 0, a, b);
11     for (unsigned int i = 1; i <= 32; i++) {
12         a = a >> 1;
13         b = b >> 1;
14         (void) printf("|%2d| 0x%08x | 0x%08x\n", i, a, b);
15     }
16 
17     return (b & a);
18 }

编译和执行,

$ gcc -g -Wall -std=gnu99 -o foo foo.c

$ ./foo
|ID|        int | unsigned int
| 0| 0x80000000 | 0x80000000
| 1| 0xc0000000 | 0x40000000
| 2| 0xe0000000 | 0x20000000
| 3| 0xf0000000 | 0x10000000
| 4| 0xf8000000 | 0x08000000
| 5| 0xfc000000 | 0x04000000
| 6| 0xfe000000 | 0x02000000
| 7| 0xff000000 | 0x01000000
| 8| 0xff800000 | 0x00800000
| 9| 0xffc00000 | 0x00400000
|10| 0xffe00000 | 0x00200000
|11| 0xfff00000 | 0x00100000
|12| 0xfff80000 | 0x00080000
|13| 0xfffc0000 | 0x00040000
|14| 0xfffe0000 | 0x00020000
|15| 0xffff0000 | 0x00010000
|16| 0xffff8000 | 0x00008000
|17| 0xffffc000 | 0x00004000
|18| 0xffffe000 | 0x00002000
|19| 0xfffff000 | 0x00001000
|20| 0xfffff800 | 0x00000800
|21| 0xfffffc00 | 0x00000400
|22| 0xfffffe00 | 0x00000200
|23| 0xffffff00 | 0x00000100
|24| 0xffffff80 | 0x00000080
|25| 0xffffffc0 | 0x00000040
|26| 0xffffffe0 | 0x00000020
|27| 0xfffffff0 | 0x00000010
|28| 0xfffffff8 | 0x00000008
|29| 0xfffffffc | 0x00000004
|30| 0xfffffffe | 0x00000002
|31| 0xffffffff | 0x00000001
|32| 0xffffffff | 0x00000000

从上面输出的结果中,我们不难看出:

  • 对于符号整数,每一次右移操作,高位补充的是1
  • 对于符号整数,每一次右移操作,高位补充的则是0

规律找到了,下面“透过现象看本质”,反汇编看看其根本原因:

 1 (gdb) set disassembly-flavor intel
 2 (gdb) disas /m main
 3 Dump of assembler code for function main:
 4 5    {
 5    0x0804841d <+0>:    push   ebp
 6    0x0804841e <+1>:    mov    ebp,esp
 7    0x08048420 <+3>:    and    esp,0xfffffff0
 8    0x08048423 <+6>:    sub    esp,0x20
 9 
10 6                 int a = 0x80000000;
11    0x08048426 <+9>:    mov    DWORD PTR [esp+0x14],0x80000000
12 
13 7        unsigned int b = 0x80000000;
14    0x0804842e <+17>:    mov    DWORD PTR [esp+0x18],0x80000000
15 ...<snip>...
16 
17 11        for (unsigned int i = 1; i <= 32; i++) {
18    0x0804847e <+97>:    mov    DWORD PTR [esp+0x1c],0x1
19    0x08048486 <+105>:    jmp    0x80484b9 <main+156>
20    0x080484b4 <+151>:    add    DWORD PTR [esp+0x1c],0x1
21    0x080484b9 <+156>:    cmp    DWORD PTR [esp+0x1c],0x20
22    0x080484be <+161>:    jbe    0x8048488 <main+107>
23 
24 12            a = a >> 1;
25    0x08048488 <+107>:    sar    DWORD PTR [esp+0x14],1
26 
27 13            b = b >> 1;
28    0x0804848c <+111>:    shr    DWORD PTR [esp+0x18],1
29 
30 14            (void) printf("|%2d| 0x%08x | 0x%08x\n", i, a, b);
31 ...<snip>...
32 18    }
33    0x080484c8 <+171>:    leave  
34    0x080484c9 <+172>:    ret    
35 
36 End of assembler dump.
37 (gdb) 

注意L24-L28,

24 12            a = a >> 1;
25    0x08048488 <+107>:    sar    DWORD PTR [esp+0x14],1
26 
27 13            b = b >> 1;
28    0x0804848c <+111>:    shr    DWORD PTR [esp+0x18],1

原来如此,对于符号整数,右移采用的是sar指令; 而对于符号整数,右移则采用的是shr指令。

sar : 算术右移 Arithmetic Right Shift | sal : 算术左移 arithmetic left shift
shr : 逻辑右移 logic Right SHift      | shl : 逻辑左移 logic left shift

推荐阅读: Arithmetic shift and Logical shift

总结:

  • 对于左移,无论是算术左移(sal)还是逻辑左移(shl),低位补充的都是0
  • 对于右移,算术右移(sar)高位补1,逻辑右移(shr)高位补0
  • 算术移位应用于有符号数,逻辑移位则应用于无符号数。 (AsLu)

转载于:https://www.cnblogs.com/idorax/p/6305476.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值