‘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
此规则查找使用指向使用 new[]
运算符分配的内存的指针的 delete
表达式。应避免此操作,因为根据 ISO/IEC C++ 标准 §5.3.5,它会导致未定义的行为。
“在第一个备选方案(delete object)中,
delete
操作数的值可以是空指针值,指向由先前 new-expression 创建的非数组对象的指针,或指向表示此类对象的基类的子对象的指针。否则,行为未定义。”
除了在形式上未定义之外,还有两个实际原因会导致此操作可能引发缺陷。首先,考虑调用 X *p = new X[23]
时会发生什么。
通过调用
::operator new(sizeof(X) * 23)
,分配了足以容纳23
个类型为X
的实例的内存。每个
X
的23
个实例都在内存中的正确位置被构造(就像执行 placement new 一样)。如果随后执行delete[] p
,则会发生相反的情况。每个
X
的23
个实例的析构函数将被调用(就像执行显式(p + i)->~X()
一样)。通过调用
::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。