探討有符號數與無符號數數據上溢出和下溢出問題


<一>下面為有符號數的溢出:

     #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

 


免責聲明!

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



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