CodeQL 文档

‘new[]’ 数组使用 ‘delete’ 释放

ID: cpp/new-array-delete-mismatch
Kind: problem
Security severity: 
Severity: warning
Precision: high
Tags:
   - reliability
Query suites:
   - cpp-security-and-quality.qls

单击查看 CodeQL 仓库中的查询

此规则查找使用指向使用 new[] 运算符分配的内存的指针的 delete 表达式。应避免此操作,因为根据 ISO/IEC C++ 标准 §5.3.5,它会导致未定义的行为。

“在第一个备选方案(delete object)中,delete 操作数的值可以是空指针值,指向由先前 new-expression 创建的非数组对象的指针,或指向表示此类对象的基类的子对象的指针。否则,行为未定义。”

除了在形式上未定义之外,还有两个实际原因会导致此操作可能引发缺陷。首先,考虑调用 X *p = new X[23] 时会发生什么。

  1. 通过调用 ::operator new(sizeof(X) * 23),分配了足以容纳 23 个类型为 X 的实例的内存。

  2. 每个 X23 个实例都在内存中的正确位置被构造(就像执行 placement new 一样)。如果随后执行 delete[] p,则会发生相反的情况。

  3. 每个 X23 个实例的析构函数将被调用(就像执行显式 (p + i)->~X() 一样)。

  4. 通过调用 ::operator delete(p),释放了由 ::operator new 分配的内存。相比之下,delete p(没有 [] 括号)通常假定 p 指向 X 的单个实例,并且只为该实例调用析构函数(尽管此行为不可靠,因为结果在形式上是未定义的)。实际结果是,其余 X 实例的析构函数将不会被调用,而这些析构函数可能执行释放资源等至关重要的操作。

此操作可能引发缺陷还有一个实际原因。为了在调用 delete[] 时调用数组元素的析构函数,实现必须知道在删除时 p 指向的数组的大小。考虑到 p 是一个指针,并且其类型中不包含数组大小信息,因此除非实现以某种方式在调用 new[] 时存储该信息,否则一般情况下该信息将不可用。这有两种常见的方法。

  • 最常见的方法是在数组开头之前分配少量的额外内存(header),并在其中存储大小。在调用 delete[] p 时,实现只需从传入的指针向后走固定的距离即可读取大小。这意味着 p 本身(指向数组第一个元素的指针)不是由 ::operator new 返回的指针,因此在上面调用 ::operator delete 是不安全的。相反,它应该在指向 header 开头的指针上调用。调用 delete p 将使用错误的地址,这可能会导致灾难性的后果。

  • 另一种不太常见的做法是存储一个指针到数组大小的映射表(如果有)。当调用 delete[] p 时,实现会查找映射表中的指针,调用相应的析构函数数量,释放内存*并从映射表中删除指针*。如果调用了 delete p,不仅可能不会调用相应的析构函数数量(如前所述),而且指针也可能不会从映射表中删除。在实际应用中,这可能不像第一种方法那样严重,但仍应避免。

警告:此检查是近似的,因此某些结果可能不是程序中的实际缺陷。通常,在不使用所有输入数据运行程序的情况下,无法计算指针的值。

建议

使用 delete[] 运算符释放使用 new[] 分配的内存。

示例

Record* record = new Record[SIZE];

...

delete record; //record was created using 'new[]', but was freed using 'delete'

参考资料

  • S. Meyers. Effective C++ 3d ed. pp 73-75. Addison-Wesley Professional, 2005.

  • ISO/IEC 14882:2011,信息技术 - 编程语言 - C++ §5.3.5。国际标准化组织,日内瓦,瑞士,2011。

  • ©GitHub, Inc.
  • 条款
  • 隐私