位運算:有符號整數右移和無符號整數右移的區別


如果我們定義一個符號整數 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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM