资源访问校验

当外部数据参与数组索引、内存申请大小、内存复制长度、指针偏移等操作,必须对数据大小进行严格校验,否则可能发生严重问题。

see also 202306272237整数运算

在传递数组时,同时传递数组长度

数组在函数传参时会退化为指针,导致数组长度信息丢失,容易造成数组索引合法性检查错误,引发越界读写问题。

void test()
{
    char array[10];
    init_array(array);
}
 
void init_array(char arr[])
{
    // 【错误】这里计算的是 char* 指针的大小
    for (size_t i = 0; i < sizeof(arr); ++i) ...
}

建议:

  • 如果函数只接受固定长度的数组作为参数,可以定义参数类型为数组引用或std::array
  • 如果函数要兼容C接口,那么应该把长度作为另外一个参数也传递进去
void init_array(char arr[], size_t len);  // ok
 
void init_arr(char arr[])  // error, arr deduced to char*
{
    cout << "nake arr" << sizeof(arr) << endl;
}
 
void init_arr_ref(char (&arr_ref)[10]) // ok, pass by ref
{
    cout << "arr ref" << sizeof(arr_ref) << endl;
}
 
template<typename T, size_t N>
void init_arr_temp(T (&arr_temp)[N])  // ok, template instantiate to init_arr_temp<char, 10UL>(char (&arr_temp)[10])
{
    cout << "arr temp" << sizeof(arr_temp) << endl;
}

RAII管理资源的声明周期

RAII

资源的获取和释放是成对操作(例如new/delete,fopen/fclose,lock/unlock等)恰好能对应c++语言对称的构造函数和析构函数。利用c++对象的生命周期来管理资源的声明周期,是一种常见的策略。

不要访问超出生命周期的对象

禁止将局部变量的地址返回到其作用域以外,

string_view get_data()
{
    const char s[] = "hello world";
    return s;  // 【错误】,会间接将s的地址返回到其作用域外
}

当lambda会逃逸处函数外面时,禁止按引用捕获局部变量,

void foo()
{
    int local_var = 0;
    ...
    // 【错误】,lambda不确定何时、在何地(线程)执行,而local_val的生命周期在此处即将结束
    thread_pool.QueueWork([&] { process(local_val); });
}