整数算术看起来像是小学科目。但是在编程中,你绝对应该不低估它。事实上,所有更安全的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检查。