一 提兩個簡單問題:
下面代碼在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內的代碼一與代碼二的輸出結果不同,代碼一在執行
short b = a + 1
語句時發生了short整型溢出問題, 代碼二是安全的。它們的執行結果分別如下:- 代碼一的輸出結果:
代碼一的輸出結果為:-32768
- 代碼二的輸出結果:
代碼二的輸出結果為:32768
- 問題2內的代碼三與代碼四分別在執行
int b = a + 1
和long 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)
從匯編代碼就可以看出來,它不會發生整數溢出了!!