CppCoreGuidelines ES.102 数值计算用有符号类型
22 May 2023
“Use signed types for arithmetic”
理由
因为大部分算术运算默认是有符号类型的。 当 y > x
时 x - y
会产生负数。除了某些特殊情况,比如求模运算,可能需要无符号类型。
例子
无符号类型的算术运算可能会导致你不期望的结果。尤其是混用了有符号与无符号类型的运算:
template<typename T, typename T2> T subtract(T x, T2 y) { return x - y; } void test() { int s = 5; unsigned int us = 5; cout << subtract(s, 7) << '\n'; // -2 cout << subtract(us, 7u) << '\n'; // 4294967294 cout << subtract(s, 7u) << '\n'; // -2 cout << subtract(us, 7) << '\n'; // 4294967294 cout << subtract(s, us + 2) << '\n'; // -2 cout << subtract(us, s + 2) << '\n'; // 4294967294 } int main() { test(); return EXIT_SUCCESS; }
-2 4294967294 -2 4294967294 -2 4294967294
这里,我们很清楚发生了什么。但是如果写成 us - (s + 2)
或者 s += 2;
...; us - s
,你很可能就不知道为什么结果会是 4294967294。
例外
如果确实需要求模运算,可以用无符号类型。但是注意写明注释,尤其当可能产生一些数值溢出行为的时候。这种代码会让程序员感到意外。
例子
标准库使用无符号类型作为下标。而内置数组类型则用有符号类型作为下标。这方面产生惊讶和 bug 在所难免:
int a[10]; for (int i = 0; i < 10; ++i) a[i] = i; vector<int> v(10); // compares signed to unsigned; some compilers warn, but we should not for (gsl::index i = 0; i < v.size(); ++i) v[i] = i; int a2[-2]; // error: negative size // OK, but the number of ints (4294967294) is so large that we should // get an exception vector<int> v2(-2);
用 gsl::index
做下标。
强化
- 标记混用有符号和无符号类型的算术计算
- 标记无符号类型的计算结果复制给有符号类型或打印成有符号类型
- 标记用负数作为容器下标的地方
- 不要标记有符号类型与无符号类型混合比值的地方,不然会有太多噪音。比如:其中一个参数是
sizeof
或容器的.size()
,而另一个参数是ptrdiff_t