23 December 2021

C++ 核心指南目录

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

强化

  • 标记出现指针+数组数量作为参数的接口。可能存在潜在数组越界访问问题。