Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deallocation of memory when argument subexpression of new expression throws

Dynamic analysis discovered curious memory leak in our code-base. The code in question looks like:

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

where the function getArgument() sometimes throws. And when it throws, the freshly allocated object is leaked. This is compiled by Visual Studio 2015 (MSC++ 19.0).

Now when I checked the specification (C++14 final draft), the §5.3.4/8 curiously says:

A new-expression may obtain storage for the object by calling an allocation function (3.7.4.1). If the new-expression terminates by throwing an exception, it may release storage by calling a deallocation function (3.7.4.2). If the allocated type is a non-array type, the allocation function’s name is operator new and the deallocation function’s name is operator delete. If the allocated type is an array type, the allocation function’s name is operator new[] and the deallocation function’s name is operator delete[].

The use of ‘may’ (highlighted above by me) here implies the compiler is free not to do it.

So is this:

  1. stated as required somewhere else in the specification, making it a bug in Visual C++ compiler (that might occur only under some condition; didn't check how general it is),
  2. a bug in the specification, or
  3. written this way for some reason?

Note: the code does correctly delete the object when the expression completes. There is no bug in that. The problem is strictly in what happens when the new-expression throws.

like image 932
Jan Hudec Avatar asked May 11 '17 12:05

Jan Hudec


2 Answers

From the latest draft, the relevant quote is on:

expr.new/8: A new-expression may obtain storage for the object by calling an allocation function ([basic.stc.dynamic.allocation]). If the new-expression terminates by throwing an exception, it may release storage by calling a deallocation function. ....

The use of "may" is in precedence of a proceeding section:

expr.new/21 If any part of the object initialization described above 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: This is appropriate when the called allocation function does not allocate memory; otherwise, it is likely to result in a memory leak. — end note  ]


But you got hit by the indeterminate sequencing of the new expression in C++14 and older; Which says:

$5.3.4/18 The invocation of the allocation function is indeterminately sequenced with respect to the evaluations of expressions in the new-initializer. Initialization of the allocated object is sequenced before the value computation of the new-expression. It is unspecified whether expressions in the new-initializer are evaluated if the allocation function returns the null pointer or exits using an exception.

taken from C++14 draft

As per the adoption of this paper. we now have a defined sequence in C++17:

expr.new/19 The invocation of the allocation function is sequenced before the evaluations of expressions in the new-initializer. Initialization of the allocated object is sequenced before the value computation of the new-expression.



like image 91
WhiZTiM Avatar answered Oct 21 '22 05:10

WhiZTiM


Check further - specifically paragraph 20 :

If any part of the object initialization described above 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: This is appropriate when the called allocation function does not allocate memory; otherwise, it is likely to result in a memory leak. — end note ]

like image 35
Sander De Dycker Avatar answered Oct 21 '22 06:10

Sander De Dycker