This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 116a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-12-19


2566. Matching deallocation for uncaught exception

Section: 7.6.2.8  [expr.new]     Status: review     Submitter: Jim X     Date: 2022-04-13

Initialization of an object may terminate via an exception, in which case any dynamically-allocated memory is freed, per 7.6.2.8 [expr.new] paragraph 26:

If any part of the object initialization described above [ Footnote: ... ] terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression. If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed.

However, implementations do not consistently support this provision in case the exception remains uncaught:

  #include <iostream>
  struct C {
    void* operator new(std::size_t n) {
      std::cout << "malloc\n";
      return malloc(n);
    }
    void operator delete(void* ptr) {
      std::cout << "free\n";
      free(ptr);
    }
    C() {
      throw 0;
    }
  };
  int main() {
    auto ptr = new C;
  }

Both clang and GCC do not free the memory in this example; they do so if the exception is caught in main.

Maybe a similar provision as used for stack unwinding in 14.4 [except.handle] paragraph 9 is desirable:

If no matching handler is found, the function std::terminate is invoked; whether or not the stack is unwound before this invocation of std::terminate is implementation-defined (14.6.2 [except.terminate]).

Suggested resolution:

Integrate freeing dynamically-allocated memory with stack unwinding (14.3 [except.ctor]), since this is what implementations actually do.

Possible resolution:

  1. Change in 7.6.2.8 [expr.new] paragraph 26, 27, and 28 as follows:

    If any part of the object initialization described above [ Footnote: This can include evaluating a new-initializer and/or calling a constructor. ] terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression. If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object's memory to be freed. [Note 13: This is appropriate when the called allocation function does not allocate memory; otherwise, it is likely to result in a memory leak. —end note]

    For purposes of stack unwinding (14.3 [except.ctor]), the matching deallocation function is determined as follows: If the new-expression does not begin with a unary :: operator and the allocated type is a class type T or an array thereof, a search is performed for the deallocation function's name in the scope of T. Otherwise, or if nothing is found, the deallocation function's name is looked up by searching for it in the global scope.

    A declaration of a placement deallocation function matches the declaration of a placement allocation function if it has the same number of parameters and, after parameter transformations (9.3.4.6 [dcl.fct]), all parameter types except the first are identical. If the lookup finds a single matching deallocation function, that function will be called is the matching deallocation function; otherwise, no deallocation function will be called there is no matching deallocation function. If the lookup finds a usual deallocation function and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. For a non-placement allocation function, the normal deallocation function lookup is used to find the matching deallocation function (7.6.2.9 [expr.delete]).

  2. Change in 14.3 [except.ctor] paragraph 1 as follows:

    As control passes from the point where an exception is thrown to a handler, objects are destroyed and deallocation functions are invoked by a process, specified in this subclause, called stack unwinding.
  3. Change in 14.3 [except.ctor] paragraph 5 as follows:

    [Note 4: If the object was allocated by a new-expression (7.6.2.8 [expr.new]), If the evaluation of a new-expression other than the invocation of the allocation function is terminated by an exception, the matching deallocation function (6.7.6.5.3 [basic.stc.dynamic.deallocation]), if any, is called (7.6.2.8 [expr.new]) to free the storage occupied by the object. end note]