<一>下面為有符號數的溢出:
#include<void.h>
Void main()
{
Int i= 2147483647;
Printf(“%d,%d”,i.i+1);
}
輸出結果為:2147483647,-2147483648
這是因為加減運算過后,它們的值超出了它們對應的那種整數類型的表示范圍,我們把這種現象稱為溢出。
注意:看清楚數字總是在循環的變化。如從最大2147483647,再加一后就變成了最小-2147483648。即循環的順序是:
0— 2147483647— -2147483648— 0。
規律:
SHRT_MAX+1 == SHRT_MIN
SHRT_MIN-1 == SHRT_MAX
例如:
#include <stdio.h>
int main(void)
{
short int a=32767,b=32767,c;
a=a+b; //下面三行代碼是實現兩數交換的功能
b=a-b;
a=a-b;
c=sizeof(short int);
printf("a=%d,b=%d\n",a,b);
printf("sizeof=%d",c);
return 0;
}
結果:a=32767,b=32767
對學習編程者的忠告:
眼過千遍不如手過一遍!
書看千行不如手敲一行!
手敲千行不如單步一行!
單步源代碼千行不如單步對應匯編一行!
考察如下程序段:
int n=1,sum=0;
while(sum<=32767) {sum+=n; n++;}
printf(“n=%d\n”,n-1);
乍看該程序時無錯誤,但事實上,上列程序中的while循環是一個無限循環,原因在於int型數的表示范圍為-32768到+32767,當累加和sum超過32767時,便向高位進位,而對int型數而言,最高位表示符號,故sum超過32767后便得到一個負數,while條件當然滿足,從而形成無限循環。此時,最好的解決辦法是將sum定義為long int型。
另外google的一道筆試題中也需要意識到溢出的存在
short cal(short x)
{
if(x==0)
return 0;
else
return x+cal(x-1);
}
答案
x==0時,0
x>0時,x+…+1
x<0時,x+(x-1)+…+(-32768)【溢出】+32767+……+1,中途棧溢出
假如是short類型的負數來說,-32768減去1之后,變成32767,就是說對於有符號整數來說:最小的負數-1=最大的整數,最大的整數+1=最小的負數。
假如棧不溢出,那么將遞歸32768-x+32767次,最后的值按照上面是可以計算出來的
但是棧的空間有限,當棧溢出的時候,錯誤,強制退出。
在gcc下,測試,假如上述數據類型是char,最后是能計算出值的大小,棧的大小還夠用。
<二>下面為無符號數的溢出:
上面提到的是有符號數的溢出,下面是無符號數的溢出
在c語言的程序開發調試中,經常碰到非法操作導致程序強行終止。這種情況的發生多是因為程序指針的指向錯誤,數據溢出就是其中的一種,下面我將介紹一下常見的幾種溢出情況。
1、無符號整數上溢
示例代碼:
bool funcB(char *s1,unsigned short int len1,char *s2,unsigned short int len2)
{
if (1 + len1 + len2 > 64)
return false;
char *buf = (char*)malloc(len1+len2+1);
if (buf) {
memcpy(buf,s1;len1);
/*函數解釋:void *memcpy(void *to , const void *from, unsigned int count) :從from指向的內存區向to指向的內存區復制count個字節;如果兩內存區重疊,不定義該內存區的定義*/
memcpy(buf+len1,s2,len2);
}
if (buf) free(buf);
return true;
}
這段代碼存在整數上溢問題,當len1等於64,len2是0XFFFF,這段代碼就會發生溢出。因為在定義為unsigned short char 類型下1+0xFFFF=0,這樣就繞過了1 + len1 + len2 > 64的條件判斷。直接導致后面的代碼中錯誤地分配64字節內存,在內存拷貝時將產生溢出錯誤。
我分析:無符號整數上溢出的意思就是:無符號整數a已達最大數,+1之后又從小開始計算:1+0xFFFF=0;不同於有符號數的是,1+0xFFFF=—0xFFFF(最小數SHRT_MAX+1 == SHRT_MIN );
2、無符號整數下溢
示例代碼:
bool funcA(unsigned int cbSize)
{
if (cbSize < 1024)
{
char *buf = new char[cbSize-1];
memset(buf,0,cbSize-1);
/*
函數解釋:void memset(void *buf, int ch, unsigned int count): 把ch的低字節復制到buf指向的內存區的前count個字節處,常用於把某個內存區域初始化已知值。
*/
delete buf;
return true;
}
else
return false;
}
這是一個整數下溢的例子。當函數調用時,如果參數cbSize賦值為0,由於參數cbSize被定義為unsigned int 型,則(cbSize-1)= (0-1) = 0XFFFFFFFF,分配如此大的內存,后果可想而知!
我分析:無符號整數下溢就是:無符號整數a為最小值0,再-1后變成最大值,例如:(0-1) = 0XFFFFFFFF;同與有符號整數的是,SHRT_MIN-1 == SHRT_MAX。
----------------------------------
#include <stdio.h>
short int fac( short int x)
{
static short int y=1;
y*=x;
return y;
}
int main(void)
{
int s=0;
short i;
for(i=1;i<=8;i++)
s+=fac(i);
printf("S=%d\n",s);
return 1;
}
運行結果:S=-19303
運行SETP:
Setp1:i=1 y=1 S=0+1=1
Setp2:i=2 y=2 S=1+2=3
Setp3:i=3 y=6 S=3+6=9
Setp4:i=4 y=24 S=9+24=33
Setp5:i=5 y=120 S=33+120=153
Setp6:i=6 y=720 S=153+720=873
Setp7:i=7 y=5040 S=873+5040=5913
Setp8:i=8 y=40320溢出
16位內存空間存儲情況:1001,1101,1000,0000(即40320的二進制表示)
反求補碼:SETP1(減1)得到:1001,1101,0111,1111
SETP2(按位取反)得到:0110,0010,1000,0000(即25216的二進制表示)
故:y=-25216 S=5913-25216=-19303
Setp9:i=9,for循環結束,執行下一句輸出:S=-19303
(40320 1001 1101 1000 0000 反碼:1110 0010 0111 1111 補碼:1110 0010 1000 0000
0110001010000000為 25216 )
我解釋:此代碼中的y值40320的意思是,原先在程序中算出來值是40320,記住此時40320就是內存中的補碼,同樣也是原碼,由於正數的原碼、反碼、補碼都相同,然而這個補碼表示的意思是:y已不再是40320,由於y為有符號整數,此時,補碼40320對應的有符號原碼是:-25216,所以才有故:y=-25216 S=5913-25216=-19303。
記住:程序中算出來的數據都是原碼。當然,算出來的正數也表示補碼!
------------------------------------
#include <stdio.h>
void main()
{
char c1 = 128;//char在此只有八位
unsigned char c2 = 257;
short s1 = 65535;
//short 在此只有16位2^16=65536 2^15=32768
65535=1111 1111 1111 1111B(已為補碼) ;由於有符號,就變成了
- 111 1111 1111 1111(已為補碼)= - 32767,則其表示的原碼為 -1. 因為 - 32767 + (-1)= - 32768.
unsigned int s2 = 65537;
printf("%d,%d,%d,%d",c1,c2,s1,s2);
}
unsigned char c2 = 257這一個
在內存中8位是表示不完的
所以需要9位的二進制才能表示它的值
但一個unsigned char只能存8位的值,所以這里就需要截斷
char c1 = 128,char這是一個有符號位的類型
所以在計算它的值的時候,需要用補碼方式計算
128在內存中是:1000 0000 (最高一位是符號位)
補碼計算,按位取反,再加1得:1000 0000=128
因其符號位是1,所以是負數:-128
輸出結果:-128,1,-1,65537