代碼質(zhì)量分析-整數(shù)處理問題

1兼耀、整形范圍

數(shù)字類型,由三個(gè)維度來定義:

  1. 整數(shù) or 浮點(diǎn)數(shù):int or float/double
  2. 有符號(hào) or 無符號(hào):signed or unsigned
  3. 長(zhǎng)度:short or long(看編譯器,此處均采用32位編譯器)

長(zhǎng)度決定了位數(shù):

  • short:2字節(jié)蕾管,即16位
  • long:== int线衫,4字節(jié),即32位

在此基礎(chǔ)上繁仁,看符號(hào):

  • 如果是有符號(hào)數(shù)涉馅,那么最高位需要表示符號(hào)(0表示正數(shù),1表示負(fù)數(shù))黄虱,可表示最大值會(huì)減半稚矿,但是可以表示負(fù)數(shù)(范圍等同于正數(shù))。
  • 如果是無符號(hào)數(shù)捻浦,那么就全部是非負(fù)數(shù)晤揣,最高位也可以用于表示數(shù)字,最大值會(huì)是有符號(hào)數(shù)的兩倍朱灿。

所以可以簡(jiǎn)單得出各個(gè)整形類型的范圍(方括號(hào)表示可不填昧识,系默認(rèn)值):

  • [signed] short [int]:-2^15 ~ 2^15-1
  • unsigned short [int]:0 ~ 2^16-1
  • [signed] int:-2^31 ~ 2^31-1
  • unsigned int:0 ~ 2^32-1
  • [signed] long [int]:-2^31 ~ 2^31-1
  • unsigned long [int]:0 ~ 2^32-1
  • [signed] long long [int]:-2^63 ~ 2^63-1
  • unsigned long long [int]:0 ~ 2^64-1

問:C語言中的uint8_t\uint_16_t\uint32_t\uint64_t是什么?

實(shí)際上就是不同位長(zhǎng)度的上述基礎(chǔ)類型盗扒,比如:

uint32_t 表示 unsigned int跪楞。

問:_t 的后綴表示什么缀去?
_t 表示這些數(shù)據(jù)類型是通過typedef定義的,而不是新的數(shù)據(jù)類型甸祭。使用他們是為了明確得定義長(zhǎng)度缕碎,避免直接使用基礎(chǔ)類型時(shí),在不同編譯機(jī)器上出現(xiàn)差異池户,從定義文件中可以窺見:

#  if  __WORDSIZE ==  64  
typedef  long  int  int64_t;  
#  else  
__extension__ 
typedef  long  long  int  int64_t;  
#  endif

提問:為什么有符號(hào)數(shù)的正數(shù)范圍是-1咏雌?

回答:因?yàn)樽罡呶挥糜诒硎痉?hào),所以-1校焦。

提問:為什么有符號(hào)數(shù)的負(fù)數(shù)范圍不用-1处嫌?

回答:因?yàn)樵谟蟹?hào)數(shù)的規(guī)則下,0出現(xiàn)了+0和-0兩個(gè)表示方法斟湃,浪費(fèi)熏迹,所以把-0(1000 0000 0000 0000 0000 0000 0000 0000)額外定義成了最小的負(fù)數(shù),也就是-2^31(實(shí)際上因?yàn)樽罡呶皇欠?hào)位凝赛,本不應(yīng)該出現(xiàn)這個(gè)數(shù))注暗。

2、常見錯(cuò)誤

2.1墓猎、無意的整數(shù)外溢(OVERFLOW_BEFORE_WIDEN)

用窄長(zhǎng)度的參數(shù)計(jì)算捆昏,然后將結(jié)果賦值給寬長(zhǎng)度的變量,如果這個(gè)計(jì)算的結(jié)果超出了窄長(zhǎng)度的范圍毙沾,其高位會(huì)被丟棄骗卜,值保留窄長(zhǎng)度的范圍內(nèi)的內(nèi)容,如果是有符號(hào)類型左胞,結(jié)果會(huì)更不可知(最高位是符號(hào)位)寇仓。

極容易忽略,人們總是按照自己數(shù)字的范圍來定義變量類型烤宙,而不會(huì)考慮他會(huì)被用于計(jì)算什么遍烦。

gcc目前無法告警,Coverity靜態(tài)分析器將發(fā)出OVERFLOW_BEFORE_WIDEN警告躺枕。

建議在對(duì)變量做計(jì)算賦值時(shí)服猪,必須考慮其計(jì)算參數(shù)的類型是否至少有一個(gè)和自己類型相同。

CR建議加上對(duì)計(jì)算時(shí)參數(shù)的類型檢查拐云。

// wrong  
uint32_t a =  123456;  
uint64_t b = a *  1000000000;  // 結(jié)果可能會(huì)溢出罢猪,b不會(huì)得到正確的結(jié)果  

// right  
uint64_t a =  123456;  
uint64_t b = a *  1000000000;  

// right  
uint32_t a =  123456;  
uint64_t b = a *  (uint64_t)1000000000;

2.2、除以零或求零的模(DIVIDE_BY_ZERO)

在計(jì)算除法或者求模的時(shí)候叉瘩,傳入的變量可能為0膳帕,從而引起不確定的行為,對(duì)C++來說房揭,會(huì)引起程序中斷备闲。

對(duì)編譯來說晌端,除數(shù)是個(gè)變量,是不會(huì)告警的恬砂。

cout <<  1  /  0  << endl;  // 編譯會(huì)報(bào)錯(cuò)  

int a =  0; cout <<  1  / a << endl;  // 編譯不會(huì)報(bào)錯(cuò)咧纠,運(yùn)行時(shí)報(bào)錯(cuò)。

本質(zhì)上是一種異常判斷不嚴(yán)謹(jǐn)?shù)那闆r泻骤,建議對(duì)所有除法和求模操作漆羔,如果對(duì)象是變量,那么必須要做非0判斷狱掂。

CR建議加上對(duì)除法/求模運(yùn)算的參數(shù)判斷檢查演痒。

2.3、不適當(dāng)?shù)厥褂昧素?fù)值(NEGATIVE_RETURNS)

通常指將一個(gè)有符號(hào)類型的參數(shù)趋惨,傳給一個(gè)無符號(hào)類型的參數(shù)鸟顺。

最容易弄錯(cuò)的是對(duì)于時(shí)間的計(jì)算:

uint32_t cur_time =  time(nullptr);  // 錯(cuò)誤  

void  SomeFunc(uint32_t time);  
SomeFunc(time(nullptr));  // 錯(cuò)誤

time(nullptr) 函數(shù)實(shí)際返回的是一個(gè) time_t 類型的結(jié)果。這個(gè)time_t類型器虾,實(shí)際上就是對(duì)long類型的一個(gè)typedef讯嫂。

typedef  long  time_t;

問:為什么time_t要被定義為一個(gè)有符號(hào)數(shù)?猜測(cè)是可以表述1970年之前的時(shí)間兆沙?

由于我們一般意義上理解time(nullptr)是一個(gè)秒數(shù)欧芽,不可能為負(fù)數(shù),所以會(huì)把它當(dāng)正數(shù)使用葛圃,實(shí)際上它的返回值是個(gè)有符號(hào)數(shù)千扔。

由此引申,其他的變量也是库正,我們可能覺得一個(gè)數(shù)一定是正數(shù)曲楚,所以把它當(dāng)無符號(hào)數(shù)用,實(shí)際上如果它被定義為有符號(hào)數(shù)诀诊,那就是有風(fēng)險(xiǎn)的洞渤。

2.4、操作數(shù)不影響結(jié)果(CONSTANT_EXPRESSION_RESULT)属瓣、宏將無符號(hào)值與 0 做了比較(NO_EFFECT)

主要是對(duì)變量的范圍做判斷時(shí),做了無效判斷讯柔。

比如判斷一個(gè)無符號(hào)數(shù)是否小于0抡蛙,或者判斷一個(gè)32位的數(shù)是否大于一個(gè)64位數(shù)的最大值等。其結(jié)果一定是否魂迄。

雖說無害燃辖,但是增加了圈復(fù)雜度婉宰。

uint32_t a =  100;  
if  (a <  0)  {xxx}  // 永遠(yuǎn)不會(huì)進(jìn)分支

2.5、邏輯與按位運(yùn)算符(CONSTANT_EXPRESSION_RESULT)

直接把數(shù)字當(dāng)做布爾型的值來計(jì)算宝与,有效但是不應(yīng)該。

如下面的用法嗓节,猜測(cè)他是要判斷ret是否等于兩者中的之一,但這種寫法,會(huì)導(dǎo)致永遠(yuǎn)會(huì)進(jìn)分支灭美。非常不應(yīng)該。

在CR時(shí)如果出現(xiàn)這種代碼昂利,相信也會(huì)很容易發(fā)現(xiàn)届腐。

if  (ret ==  269807148  ||  269807149)  {  
  return ret;  
}

2.6、非正常符號(hào)擴(kuò)展(SIGN_EXTENSION)

這里涉及的其實(shí)是有符號(hào)數(shù)和無符號(hào)數(shù)在不同長(zhǎng)度的類型之間轉(zhuǎn)換時(shí)的問題蜂奸。

我們分成幾類:

// 1. 無符號(hào)數(shù)轉(zhuǎn)為更長(zhǎng)的無符號(hào)數(shù)  
uint8_t a =  5;  // 00000101  
uint16_t b = a;  // 0000000000000101犁苏,b也會(huì)是5  

// 2. 無符號(hào)數(shù)轉(zhuǎn)為更短的無符號(hào)數(shù)  
uint16_t a =  1021;  // 0000001111111101  
uint8_t b = a;  // 11111101,b會(huì)變成253  

// 3. 有符號(hào)數(shù)轉(zhuǎn)為更長(zhǎng)的有符號(hào)數(shù)  
int8_t a =  -5;  // 10000101  
int16_t b = a;  // 1111111110000101扩所,b也會(huì)是-5  

// 4. 有符號(hào)數(shù)轉(zhuǎn)為更短的有符號(hào)數(shù)  
int16_t a =  1925;  // 0000011110000101  
int8_t b = a;  // 10000101围详,由于符號(hào)位的存在,b變成-5祖屏,不但數(shù)值被縮短了短曾,正負(fù)也變了  

// 5. 有符號(hào)數(shù)變?yōu)闊o符號(hào)數(shù)  
int8_t a =  -20;  // 10010100  
uint8_t b = a;  // 10010100,由于符號(hào)位被當(dāng)做數(shù)據(jù)位赐劣,b變成148  

// 6. 無符號(hào)數(shù)變?yōu)橛蟹?hào)數(shù)  
uint8_t a =  148;  // 10010100  
int8_t b = a;  // 10010100嫉拐,由于最高位被視為符號(hào)位,b變成-20  

// 7. 有符號(hào)和有符號(hào)數(shù)的計(jì)算  
int8_t a =  -84;  // 11010100  
int8_t b =  -84;  // 11010100  
int8_t c = a + b;  // 首先正常計(jì)算結(jié)果-168超過了8位魁兼,1000000010101000  
// 由于結(jié)果是8位婉徘,所以被截?cái)嗪螅S嗟?0101000咐汞,結(jié)果變成了-40  

// 8. 無符號(hào)和無符號(hào)數(shù)的計(jì)算  
int8_t a =  212;  // 11010100  
int8_t b =  212;  // 11010100  
int8_t c = a + b;  // 首先正常計(jì)算結(jié)果424超過了8位盖呼,0000000110101000  
// 由于結(jié)果是8位,所以被截?cái)嗪蠡海S嗟?0101000几晤,結(jié)果變成了168  

// 9. 有符號(hào)數(shù)和無符號(hào)數(shù)的計(jì)算  
uint8_t a =  6;  // 00000110  
int8_t b =  -20;  // 10010100 bool c =  (a + b)  >  6;  
// 正常的理解c應(yīng)該是false,a+b=-14  
// 但實(shí)際上計(jì)算式由于兩個(gè)參數(shù)類型不同植阴,會(huì)先進(jìn)行隱式類型轉(zhuǎn)換蟹瘾,有符號(hào)數(shù)會(huì)轉(zhuǎn)為無符號(hào)數(shù)  
// 于是結(jié)果b變成了148,相加后掠手,結(jié)果必然大于6憾朴,c變成true

綜上可知,在寫代碼時(shí)要盡量避免以下行為:

  1. 將長(zhǎng)的類型賦值給短的類型喷鸽;
  2. 在有符號(hào)和無符號(hào)類型之間做轉(zhuǎn)換(尤其是有負(fù)數(shù)存在時(shí))众雷;
  3. 對(duì)有符號(hào)和無符號(hào)類型的參數(shù)做運(yùn)算(尤其是有負(fù)數(shù)存在時(shí));
  4. 做計(jì)算時(shí),盡量用可以容納結(jié)果范圍的類型去存儲(chǔ)結(jié)果砾省。

PS:C對(duì)類型隱式轉(zhuǎn)換的順序?yàn)椋?/p>

double > float > unsigned long > long > unsigned int > int

即操作數(shù)類型排在后面的與操作數(shù)類型排在前面的進(jìn)行運(yùn)算時(shí)鸡岗,排在后面的類型將隱式轉(zhuǎn)換為排在前面的類型。

2.7编兄、錯(cuò)誤的移位操作(BAD_SHIFT)

在做移位操作時(shí)轩性,如果被移位的數(shù)以及被賦結(jié)果的變量是低位數(shù),移動(dòng)的位置是個(gè)高位數(shù)翻诉,就可能出現(xiàn)不可預(yù)知的結(jié)果炮姨。比如:

uint64_t a =  0;  
// 此處省略一些對(duì)a的修改操作  
uint32_t b =  1  << a;  // 由于a是64位,當(dāng)對(duì)1左移超過31位時(shí)碰煌,就可能發(fā)生不可知的結(jié)果

只需在申明移位的數(shù)量的變量時(shí)舒岸,注意其長(zhǎng)度不要超過允許的長(zhǎng)度即可。

另外芦圾,如果要做移位操作蛾派,最好使用無符號(hào)數(shù),避免移位后出現(xiàn)符號(hào)位的數(shù)字个少。

2.8洪乍、常量表達(dá)式結(jié)果(CONSTANT_EXPRESSION_RESULT)

一種看似正常,實(shí)際上存在邏輯問題的表達(dá)式夜焦,其判斷結(jié)果永遠(yuǎn)為true或false壳澳。

舉個(gè)例子:

if  (ret != comm::AAA || ret != comm::BBB)  {  
  // do something  
}

看似是想說如果ret不等于這兩個(gè)結(jié)果就做某事,實(shí)際上因?yàn)閞et永遠(yuǎn)不可能同時(shí)等于兩個(gè)值茫经,因此這兩個(gè)條件至少有一個(gè)成立巷波,也就是這個(gè)分支判斷永遠(yuǎn)為true。

2.9卸伞、格式化輸出

打印日志時(shí)抹镊,對(duì)于整形,需要使用對(duì)應(yīng)的格式符來輸出參數(shù)內(nèi)容荤傲。

比如不要對(duì)無符號(hào)數(shù)使用%d垮耳,應(yīng)該使用%u。

如果對(duì)整形打印時(shí)使用了%s遂黍,那還可能會(huì)直接報(bào)錯(cuò)(編譯無法告警)终佛。

3、編譯告警情況

各個(gè)問題是否在編譯時(shí)會(huì)給出告警妓湘?

問題 是否編譯告警
無意的整數(shù)外溢(OVERFLOW_BEFORE_WIDEN)
除以零或求零的模(DIVIDE_BY_ZERO)
不適當(dāng)?shù)厥褂昧素?fù)值(NEGATIVE_RETURNS)
操作數(shù)不影響結(jié)果(CONSTANT_EXPRESSION_RESULT)
非正常符號(hào)擴(kuò)展(SIGN_EXTENSION)
錯(cuò)誤的移位操作(BAD_SHIFT)
常量表達(dá)式結(jié)果(CONSTANT_EXPRESSION_RESULT)
格式化輸出

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末查蓉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子榜贴,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唬党,死亡現(xiàn)場(chǎng)離奇詭異鹃共,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)驶拱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門霜浴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蓝纲,你說我怎么就攤上這事阴孟。” “怎么了税迷?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵永丝,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我箭养,道長(zhǎng)慕嚷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任毕泌,我火速辦了婚禮喝检,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撼泛。我一直安慰自己挠说,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布愿题。 她就那樣靜靜地躺著损俭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抠忘。 梳的紋絲不亂的頭發(fā)上撩炊,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音崎脉,去河邊找鬼拧咳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛囚灼,可吹牛的內(nèi)容都是我干的骆膝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼灶体,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼阅签!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蝎抽,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤政钟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體养交,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡精算,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碎连。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灰羽。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鱼辙,靈堂內(nèi)的尸體忽然破棺而出廉嚼,到底是詐尸還是另有隱情,我是刑警寧澤倒戏,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布怠噪,位于F島的核電站,受9級(jí)特大地震影響峭梳,放射性物質(zhì)發(fā)生泄漏舰绘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一葱椭、第九天 我趴在偏房一處隱蔽的房頂上張望捂寿。 院中可真熱鬧,春花似錦孵运、人聲如沸秦陋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驳概。三九已至,卻和暖如春旷赖,著一層夾襖步出監(jiān)牢的瞬間顺又,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國打工等孵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稚照,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓俯萌,卻偏偏與公主長(zhǎng)得像果录,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咐熙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容