抛出新表达式的参数子表达式时释放内存

Deallocation of memory when argument subexpression of new expression throws

本文关键字:表达式 释放 内存 参数      更新时间:2023-10-16

动态分析在我们的代码库中发现了奇怪的内存泄漏。有问题的代码如下所示:

Something *p = new Something(getArgument());

函数getArgument()有时会抛出的位置。当它抛出时,新分配的对象会泄漏。这是由Visual Studio 2015(MSC++ 19.0)编译的。

现在,当我检查规范(C++14 最终草案)时,§5.3.4/8 好奇地说:

新表达式可以通过调用分配函数(3.7.4.1) 来获取对象的存储。如果new-expression通过抛出异常终止,它可能会通过调用释放函数来释放存储 (3.7.4.2). 如果分配的类型是非数组类型,则分配函数的名称为operator new和 释放函数的名称是operator delete。如果分配的类型是数组类型,则分配 函数的名称是operator new[],释放函数的名称是operator delete[]

在这里使用"可能"(我在上面突出显示)意味着编译器可以自由地不这样做。

这也是这样的:

  1. 在规范中的其他地方根据需要声明,使其成为Visual C++编译器中的错误(可能仅在某些条件下发生;没有检查它的通用性),
  2. 规范中的错误,或
  3. 出于某种原因这样写?

注意:当表达式完成时,代码会正确删除对象。这其中没有错误。问题严格在于新表达式抛出时会发生什么。

从最新草案来看,相关报价如下:

expr.new/8:新表达式可以通过调用分配函数来获取对象的存储 ([basic.stc.dynamic.allocation])。如果新表达式的终止时间为 引发异常,它可能会通过调用 释放功能。....

"可以"的使用优先于程序部分:

expr.new/21 如果 上述对象初始化通过抛出 异常和合适的释放函数可以找到, 调用释放函数以释放对象所在的内存 正在构建中,之后异常继续 在新表达式的上下文中传播。如果没有明确的 可以找到匹配的释放函数,传播异常 不会导致释放对象的内存。[ 注意:这是 当调用的分配函数未分配时适用 记忆;否则,可能会导致内存泄漏。— 尾注  ]


但是你被 C++14 及更早的新表达的不确定序列击中了;其中说:

$5.3.4/18 分配函数的调用是不确定排序的 关于新初始值设定项中表达式的计算。 分配对象的初始化在值之前排序 新表达式的计算。目前尚不清楚是否 如果分配,则计算新初始值设定项中的表达式 函数返回空指针或使用异常退出。

摘自 C++14 草案

根据本文的通过。 我们现在在 C++17 中有一个定义的序列:

expr.new/19 分配函数的调用在 新初始值设定项中表达式的计算。初始化 分配的对象在计算 新表达。



进一步检查 - 特别是第 20 段:

如果上述对象初始化的任何部分通过抛出异常而终止,并且可以找到合适的释放函数,则调用释放函数以释放构造对象的内存,之后异常继续在新表达式的上下文中传播。如果找不到明确的匹配释放函数,则传播异常不会导致释放对象的内存。[ 注意:当调用的分配函数不分配内存时,这是合适的;否则,可能会导致内存泄漏。— 尾注 ]