CppCoreGuidelines P.6 不能在编译时检查的,要能在运行时检查
P.6 What cannot be checked at compile time should be checkable at run time
理由
如果程序里有很难检查到的错误的话,可能会在特定的条件下,导致莫明其妙的错误。
注意
理想情况下,我们应该尽量在编译时和运行时检测到这些错误。不过,有时候在编译时很难捕捉到所有的错误。而在运行时又经常没法捕捉到所有剩下的错误。尽管如此,我们写的代码应该尽量可以进行错误检查。当然,错误检测是需要代价的,如计算资源、计算时间等。
例子
void f(int* p) { cout << p[50]; } void g(int n) { // bad: the number of elements is not passed to f() f(new int[n]); } int main() { g(3); return 0; }
-2043739824
以上代码打印出了一个莫明其妙的数字。因为数组大小没有传给 f
,在 f
中我们又允许它可以访问 p
的任意元素,当我们访问的元素超出了数组的范围时,就的到了一个垃圾数据。当然,其实这个数据也不是垃圾,它可能是别的数组的元素,或者可能是一条 CPU 指令。
例子
void f2(int* p, int n) { cout << p[n]; } void g2(int n) { f2(new int[n], 10); // bad: a wrong number of elements can be passed to f() } int main() { g2(3); return 0; }
1647575376
上面代码虽然添加了数组大小作为参数,但是这个参数的值可以是任何数字,一旦数值给错,就访问越界了,所以也不安全。
void f3(unique_ptr<int[]> p, int n) { cout << p[n]; } void g3(int n) { f3(make_unique<int[]>(n), 10); // bad: pass ownership and size separately } int main() { g3(3); return 0; }
-1123024560
独占指针(unique_ptr
)可以用来传递指针所有权。但是上面还是单独传递了数组大小参数给 f3
。错误的参数,依然会导致越界访问数组。
void f4(vector<int>& v) { cout << v[5]; } void f5(gsl::span<int> v) { cout << v[5]; } void g3(int n) { vector<int> v(n); f4(v); // pass a reference, retain ownership f5(gsl::span<int>{v}); // pass a view, retain ownership } int main() { g3(9); return 0; }
00
以上代码,通过引用和 span
视图的方式传递参数,数组大小是 span
对象自身包含的数据,可以在运行时检查是否越界访问了。
以下代码使用 vector
对象能够传递所有权,并且同时保留了大小信息:
vector<int> f5(int n) // OK: move { vector<int> v(n, 100); // ... initialize v ... return v; } int main() { auto v5 = f5(5); cout << v5.size() << endl; cout << v5.at(4) << endl; }
5 100
但是,以下例子中, f6
传递独占指针(unique_ptr<int[]>
)或所有权指针(owner<int*>
)出来,也会丢失数组大小信息。而且,所有权指针相比独占指针不会自动进行内存销毁,如果你忘记 delete
还会导致内存泄漏。
unique_ptr<int[]> f6(int n) // bad: loses n { auto p = make_unique<int[]>(n); // ... initialize *p ... return p; } owner<int*> f7(int n) // bad: loses n and we might forget to delete { owner<int*> p = new int[n]; // ... initialize *p ... return p; } int main() { auto v6 = f6(5); cout << v6[6] << endl; // out of range auto v7 = f7(5); cout << v7[6] << endl; // out of range delete v7; }
1982981225 1983112303
强化
- 标记出现指针+数组数量作为参数的接口。可能存在潜在数组越界访问问题。