C語言中有無符號類型轉換和截斷問題,很容易入坑!!!

基礎知識複習:

  1. 正數在內存中的表示形式:以原碼錶示的,比如1在在32位機器上爲0x00000001
  2. 負數:以補碼錶示的,比如-1在32位機器的表示是0xffffffff(最高位表示符號位,關於補碼錶示,看我後面的參考鏈接)
  3. unsigned char轉更長字節的類型比如unsigned int,因爲是無符號數(總是大於等於0的數)轉換,則在高位補0即可,比如unsigned char a=0x01;unsigned int(a)的值就是0x00000001
  4. unsigned int轉unsigned char,直接保留低位。比如unsigned int a=1(內存中是0x00000001),(unsigned  char)a的值還是1(內存中是0x01,即僅保留了最低的8個位)
  5. char轉int型(都是有符號型類型之間的轉換),那就可能是負數轉換,或者正數轉換了。比如char a=0x01(因爲高位爲0,說明這是個正數了,而正數原碼和補碼一樣),轉成(int)a的值就是0x00000001(因爲是正數,所以還是高位補0);但是若char a=0xff(十進制的-1),(int)a的值就是0xffffffff(因爲是負數,所以高位補1)
  6. int型轉char型,還是直接保留低位,比如int a=1(0x00000001);(char)a的值是1(0x01),這是對的,也符合我們的截斷思維。若八位 a=-2,即1111 1110轉爲4位,直接截斷得到第四位,即1110,這個數對應的值還是-2(1110是有符號數,那麼怎麼還原知道表示多少呢,就再求一次它的補碼即可,或者(逆過程)先-1再求反碼即得原碼,除了符號位不變,其他位取反後加1,得到1010,最高位是符號位,說明這是負數,這就是-2,),說明截斷後還是正確的值,還是符合我們自然的思維。
  7. 負數(最高位爲1表負數)在內存中是以原碼的補碼存在的,如

-5表示爲( 原碼):1000 0101 ===>  反碼 :1111 1010  ===>  補碼:1111 1011

在java中大數據強轉位小範圍數據類型:去高位

  浮點型轉爲整型:去小數位,再去高位

 

 

正文:

  1. 不同長度類型變量的運算(大於、小於、不等於、加減等都是運算)規則:如果操作數中存在至少一個無符號數,則所有操作數都被轉化爲無符號數,舉例:int a = -1;unsigned int b = 2;a / b;此時運算過程會自動把-1當作無符號數來對待(0xffffffff)那這個數就是一個非常大的正數了,然後做除法,得到的就不是-0.5了,這樣就是錯的。同理,如果都是有符號數,那這個運算就是正確的,比如int a = -1;int b = 2;a / b;這樣就能得到正確結果,所以同符號型數做運算沒有問題
  2. C語言隱式轉換規則簡單來說就是先進行整型提升,再進行類型對齊。類型對齊時以size最大的類型爲基準進行提升。

    對任何一個混合運算表達式,如果表達式中沒有比int型更高的類型,則所有參與運算的數值先轉換成int型後在進行運算。類型提升的過程中不會發生任何精度損失。

 

 

現在我們來分析幾個實際中遇到的例子:

例:unsgined int a=6;

int b=-20;

char c;

(a+b>6)?(c=1):(c=0);    // a+b的過程就是進行了都變爲unsigned int型運算的,導致負數的b出了問題

實際輸出c=1;因爲a+b,b先轉換爲unsigned int變爲一個非常大的正數,所以a+b>6成立

 

例:

#include<iostream>
using namespace std;

int main()
{
    unsigned uint = 10;
    if(uint>-1)    // 大於小於加減等等都是運算符,這裏都自動轉爲了uint型比較,而-1的內存中爲0xffffffff,當看出無符號型,這就是一個非常大的數,當然這裏得判斷返回就是false了
    {
        cout << "yes" << endl;
    }
    else
    {
        cout << "no" << endl;
    }
    return 0;
}
如上這段代碼,比較一個無符號數 10 和一個負數 -1 ,最後的輸出結果卻是:

no
1
10 > -1 是很顯然的事,但是在程序中無符號數和有符號的負數之間進行比較時卻出現了問題。
 

注意:char在很多編譯器中默認是有符號signed char類型,而在單片機keil中char默認是unsigned char類型!!!

例:在32位單片機中,char a=-1;if(a==-1),這裏返回的是false,爲什麼呢,因爲機器是32位(編譯器也這樣認爲的)的,這裏的常數-1就會被當作32位的數對待,即int -1,是0xffffffff(補碼存在),而char a=-1的過程是把0xffffffff截斷得到低8位爲0XFF賦值給無符號型a,值爲0xff,然後根據上面說的,現在是無符號和有符合兩個數做運算,會轉爲無符號型,即擴充到大類型的無符號型,即unsigned int,a被擴爲0x000000ff(因爲a是無符號型,即機器當作它爲正數,正數擴充是高位補0),此時0x000000ff和0xffffffff(-1的補碼)比較,當然就是false了。但是當明確說明signed char a=-1,這時候比較就是true了,因爲負數擴充前面是補1,所以就是相等了。

 

這裏也說明一個問題,不同編譯器會認爲常數有不一樣的類型對待,比如代碼裏的常數5,如果是32位單片機的編譯器keil會認爲是16位的short型的5,而一個更大的數99999可能keil又編譯爲32位的int型(或者unsigned int型)這都是編譯器自動的而且都有可能的,而常數5在8位單片機可能就會認爲是unsigned char 5,所以爲了不產生歧義和便於移植,程序中用到常數都是直接在常數末尾加一個後綴表示,其中的u爲unsigned,l爲long,f爲float,而浮點型常量後綴只有f或F,l或L,沒有u或U,因爲浮點數一般都爲有符號。即常使用的有u,ul,ui。比如最常見的0u。習慣性會在大數後面加ul(注,這是好習慣,有利於平臺移植,也能防止溢出)。

1、C語言中,常數分爲整型和浮點型。

2、默認存儲類型

整型:signed int

浮點型:double

注意:整形和浮點型的數是能直接比較大小的

 

再舉一個我自己剛剛遇到的問題例子:

unsigned int a=2;

unsigned int b=5;

int c = a-b;

此時調試發現c並不等於-2,其實用上面的理論來分析,就是,a-b運算屬於同類型同符號數的運算(會向上轉類型,已經都是int型了,所以就不用再轉了,而也都是同符號型,所以符號也不用轉了),得出的結果也會用一個同類型同符號的變量(這個同類型同符號的unsigned int是無法存放-2的,所以造成溢出)來暫存,然後再賦值給c的,這樣一個溢出的變量賦值給c就是錯誤的結果了。只有這樣改一下即可int c = (int)a-(int)b;即先強制轉換一下,而且兩個都得強轉爲int,因爲根據上面一條理論,混合符號運算會轉爲無符號型,這裏兩個都轉爲int型,那就是同有符號型的了,就能對了。

 

所以對於32位的cpu,定義變量最好就定義爲int型,因爲cpu總線寬度就是32位,一次內存讀取就是讀取到4個字節,這樣直接就能得到需要的變量數據,如果定義成long long型8個字節,cpu就得訪問內存讀取兩次才能得到需要的變量數據。如果爲了節省內存,定義爲char型,由於cpu一次讀取4個字節,實際上這時候還需要截斷得到低位才能得到需要的變量數據,也可能會造成性能損耗。所以內存夠用情況下,32位cpu就一般定義爲4字節的int型使用,cpu性能纔是最高的,且由於代碼中的常數也一般是int型,所以這樣同類型同符號數之間的運算基本不會出那些隱藏的莫名其妙問題。

 

總結:把握好核心思想:混合符號型(至少有一個無符號型)數字運算,會自動轉爲無符號型不同大小類型之間運算會提升爲最大類型

類型從小往大轉,從大類型往小轉,或者從小往大轉(負數),或者從大類型往小轉(負數),都是對的,符合我們的自然思維。

1。操作數全爲有符號數,即使類型大小不一樣,沒有問題

2。操作數全爲無符號數,即使類型大小不一樣,沒有問題

3。操作數混合了有符號數,無符號數,如果有正數有負數,很有可能出問題

 

所以寫代碼時候,一定要同符號類型(同時有符號還是無符號變量或者常數)進行運算,可以避免正負數問題帶來的錯誤問題,這樣可以減少這些隱形難以發現的錯誤。

同時最好同大小類型(同時int,char,double等),這樣可以避免截斷時候溢出等可能帶來的錯誤。

 

非常值得一看的參考文章:

負數在計算機中如何表示,計算機中負數爲什麼用補碼錶示?

keil對數值常量 缺省處理都是int型嗎?

用“UL”避免Keil C51大整數常量運算溢出錯誤

C語言中常量後綴,u或U,l或L,f或F問題

C陷阱篇之char的默認符號

C語言中在常數後面加U、L、F的功能(轉)

浮點數在計算機中存儲方式

【c++】無符號類型與負數的比較

C語言類型轉換----有符號數截斷問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章