整數算術看起來像是小學科目。但是在編程中,你絕對應該不低估它。事實上,所有更安全的C語言子集(例如,MISRA-C:2012 [1])都有完整的整數算術規則類別。有趣的是,在嵌入式開發中,這些往往是幾乎所有代碼庫中最常違反的規則。那么,是什么讓整數運算如此棘手呢?C語言規則是什么?如何避免一些最常見的陷阱?
為什么整數很棘手?
數學中的整數很簡單,因為只有一種:無限數可以從-∞ 至+∞. 但在編程中,數字的范圍有限,例如8位、16位、32位或64位。每個范圍都需要特殊處理。此外,數字可以分配不同的符號(有符號和無符號)。這意味著數字表示中的相同位模式可以根據分配給數字的范圍和有符號性進行不同的解釋。例如,位模式0xFFF6可以解釋為65526個16位無符號值、-10個有符號16位值或65526個有符號32位值。現在,考慮在表達式中混合所有這些不同整數類型的含義!
標準<stdint.h>整數
簡化整數問題的第一步是知道給定數字的范圍和有符號性。但是,“C-native”整數類型(如“int”和“char”以及“short”、“long”和“unsigned”限定符)故意模棱兩可。例如,“int”在8位或16位CPU上可能具有16位范圍,在32位CPU上具有32位范圍,而在64位CPU上則具有64位范圍。
幾十年來,這種模糊性一直是一個大問題,尤其是在嵌入式編程中。但最后,C99[2]正式解決了這個問題,它引入了頭文件<stdint.h>中定義的標準整數類型。<stdint.h>中的整數示例包括:uint8_t、int16_t或uint32_t。
對于嵌入式開發人員,<stdint.h> 可以說是C99中最有價值的特性,因為它具有標準化的名稱,更重要的是,因為現在編譯器供應商負責提供具有已知范圍和符號的可移植整數類型。但要利用這一點,需要使用<stdint.h> 整數而不是普通的“int”、“short”或“char”發明自己的整數類型名稱會適得其反。
C中混合整數類型和整數提升規則
雖然<stdint.h> 整數肯定有幫助,為了理解表達式的計算方式,你仍然需要知道C[2]中的整數提升規則,這取決于CPU。當在表達式中混合不同的整數類型時,尤其如此。
C中有符號和無符號整數常量
C中的純整數常量表示有符號整數。例如,常數0表示有符號整數零(嚴格來說,有符號八進制零)。但也可以通過提供后綴“U”或“u”來生成顯式無符號常量。例如,0U表示無符號整數零。其他示例包括1U、5u、0xDEADEAFU。為無符號整數常量應用后綴“U”(或“u”)是改進整數表達式的一種簡單方法。
結束注釋
整數運算是C中bug的沃土,盡管是擁有幾十年經驗的嵌入式開發人員,也仍然會犯整數錯誤。使用靜態分析工具也是減少整數錯誤的一種非常有效的技術。運行靜態分析時,還應啟用MISRA-C檢查。