整數溢出問題的坑,你真弄明白了嗎?


一 提兩個簡單問題:

下面代碼在64位系統下運行,short 類型占兩個字節,int類型占4個字節,long類型占8個字節, 猜猜問題1與問題2的結果:

  • 問題1:以下兩個代碼的輸出結果相同嗎
    • 代碼一:
    int main()
    {
        short a = 0x7fff;
        short b = a + 1;
        int c = b;
        printf("代碼一的輸出結果為:%d\n", c);
        return 0;
    }
    
    • 代碼二:
    int main()
    {
        short a = 0x7fff;
        int c = a + 1;
        printf("代碼二的輸出結果為:%d\n", c);
        return 0;
    }
    
  • 問題2:以下的代碼輸出結果又是否相同?
    • 代碼三:
    int main()
    {
        int a = 0x7fffffff;
        int b = a + 1;
        long c = b;
        printf("代碼三的輸出結果為:%ld\n", c);
    }
    
    • 代碼四:
    int main()
    {
        int a = 0x7fffffff;
        long c = a + 1;
        printf("代碼四的輸出結果為:%ld\n", c);
    }
    

二 猜對答案了沒?

  1. 問題1內的代碼一與代碼二的輸出結果不同,代碼一在執行short b = a + 1語句時發生了short整型溢出問題, 代碼二是安全的。它們的執行結果分別如下:
    • 代碼一的輸出結果:
    代碼一的輸出結果為:-32768
    
    • 代碼二的輸出結果:
    代碼二的輸出結果為:32768
    
  2. 問題2內的代碼三與代碼四分別在執行int b = a + 1long c = a + 1時都發生了整數溢出問題,它們的執行結果是相同的:
    • 代碼三的輸出結果:
    代碼三的輸出結果為:-2147483648
    
    • 代碼四的輸出結果:
    代碼四的輸出結果為:-2147483648
    

三 原因分析:

  • 代碼一分析:
    在代碼一內,執行short b = a + 1時發生溢出應該是很容易理解,short最大表示的正整數為0x7fff,加一之后變為了0x8000, 即能表示的最大負整數-32768, 再轉換為int類型時,符號位是要保留的,可以還是-32768, 看看它的匯編代碼更容易理解:
    subq    $16, %rsp
    movw    $32767, -12(%rbp)
    // 該指令操作之后,eax寄存器的高16位為0補上(movzwl中的z就表示zero),低16的ax的值為32767.
    movzwl  -12(%rbp), %eax
    addl    $1, %eax
    // 關鍵: 這里只把ax的值(即相加之后的32768,0x8000)保存到了內存中。
    movw    %ax, -10(%rbp)
    // 下面是在執行short類型到int類型的轉換,其中eax中的高16的值是使用符號位進行填充(movswl的s對應sign),0x8000符號位為1.
    movswl  -10(%rbp), %eax
    movl    %eax, -8(%rbp)
    
  • 代碼二分析:
    在代碼二內,當執行int c = a + 1時沒有發生short類型的溢出問題, 原因還得從匯編代碼層次查找到:
    subq	$16, %rsp
    movw	$32767, -6(%rbp)
    movswl	-6(%rbp), %eax      // 以符號位填充高16位的數式復制到eax寄存器
    addl	$1, %eax
    movl	%eax, -4(%rbp)
    
  • 代碼三分析:
    在代碼三中,當執行'int b = a + 1`時發生了int整數的溢出,它對應的匯編代碼如下所示:
    subq	$16, %rsp
    movl	$32767, -16(%rbp)
    movl	-16(%rbp), %eax     // 下面三個代碼使用eax寄存器執行了加1操作
    addl	$1, %eax
    movl	%eax, -12(%rbp)
    movl	-12(%rbp), %eax
    cltq                        // 下面兩行,使用rax寄存器執行了類型轉換操作,
    movq	%rax, -8(%rbp)      // cltq執行符號位擴展,把eax擴展為rax,高32使用32位填充。
    
  • 代碼四分析:
    在代碼四中,當執行'long c = a + 1`時也發生了int整數的溢出,奇怪吧。來看看它對應的匯編代碼:
    subq	$16, %rsp
    movl	$32767, -12(%rbp)
    movl	-12(%rbp), %eax
    addl	$1, %eax            // 執行相加過程也是使用eax寄存器,因此就溢出了。
    cltq
    movq	%rax, -8(%rbp)
    

四 如何解決整數溢出問題?

在對操作數執行運算之前,先進行類型轉換,而不是執行了運算之后再執行類型轉換
例如針對代碼四的整數溢出問題,代碼修改如下:

int main()
{
    int a = 0x7fffffff;
    long c = (long)a + 1;
    printf("代碼四的輸出結果為:%ld\n", c);
}

代碼修改之后,它對應的匯編代碼如下:

subq	$16, %rsp
movl	$32767, -12(%rbp)
movl	-12(%rbp), %eax
cltq
addq	$1, %rax
movq	%rax, -8(%rbp)

從匯編代碼就可以看出來,它不會發生整數溢出了!!


免責聲明!

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



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