bugprone-multiple-new-in-one-expression

在单个表达式中查找多个 new 运算符调用,如果第二个分配失败并抛出异常,则第一个 new 分配的内存可能会泄漏。

C++ 通常不指定运算符操作数或函数参数的精确求值顺序。因此,如果第一个分配成功而第二个分配失败,则在异常处理程序中无法确定哪个分配失败并释放内存。即使顺序已固定,第一个 new 的结果也可能存储在一个在第二个分配失败时无法访问的临时位置。如果使用异常处理来检查分配错误,最好避免任何包含多个 operator new 调用的表达式。

短路运算符 ||&& 以及 , 运算符适用不同的规则,其中必须完成一边的求值才能开始另一边的求值。列表初始化的表达式(使用 {} 字符进行初始化或构造)按固定顺序求值。类似地,? 运算符的条件将在分支求值之前求值。

如果两个 new 调用出现在运算符的两侧的单个表达式中,或者如果 new 调用出现在函数调用的不同参数中(可以是使用 () 语法的对象构造),则检查会报告警告。这些 new 调用可以在任何级别嵌套。为了发出任何警告,new 调用应该位于使用 std::bad_allocstd::exception 捕获异常处理的代码块中。在 ||&&,?(条件和一个分支)运算符中不会发出警告。如果两个内存分配都没有分配给变量或没有直接传递给函数,则不会发出警告。原因是,在这种情况下,内存可能故意没有被释放,或者分配的对象可能是自毁对象。

示例

struct A {
  int Var;
};
struct B {
  B();
  B(A *);
  int Var;
};
struct C {
  int *X1;
  int *X2;
};

void f(A *, B *);
int f1(A *);
int f1(B *);
bool f2(A *);

void foo() {
  A *PtrA;
  B *PtrB;
  try {
    // Allocation of 'B'/'A' may fail after memory for 'A'/'B' was allocated.
    f(new A, new B); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined

    // List (aggregate) initialization is used.
    C C1{new int, new int}; // no warning

    // Allocation of 'B'/'A' may fail after memory for 'A'/'B' was allocated but not yet passed to function 'f1'.
    int X = f1(new A) + f1(new B); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined

    // Allocation of 'B' may fail after memory for 'A' was allocated.
    // From C++17 on memory for 'B' is allocated first but still may leak if allocation of 'A' fails.
    PtrB = new B(new A); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception

    // 'new A' and 'new B' may be performed in any order.
    // 'new B'/'new A' may fail after memory for 'A'/'B' was allocated but not assigned to 'PtrA'/'PtrB'.
    (PtrA = new A)->Var = (PtrB = new B)->Var; // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined

    // Evaluation of 'f2(new A)' must be finished before 'f1(new B)' starts.
    // If 'new B' fails the allocated memory for 'A' is supposedly handled correctly because function 'f2' could take the ownership.
    bool Z = f2(new A) || f1(new B); // no warning

    X = (f2(new A) ? f1(new A) : f1(new B)); // no warning

    // No warning if the result of both allocations is not passed to a function
    // or stored in a variable.
    (new A)->Var = (new B)->Var; // no warning

    // No warning if at least one non-throwing allocation is used.
    f(new(std::nothrow) A, new B); // no warning
  } catch(std::bad_alloc) {
  }

  // No warning if the allocation is outside a try block (or no catch handler exists for std::bad_alloc).
  // (The fact if exceptions can escape from 'foo' is not taken into account.)
  f(new A, new B); // no warning
}