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 isoperator delete
. If the allocated type is an array type, the allocation function’s name isoperator new[]
and the deallocation function’s name isoperator delete[]
.
The use of ‘may’ (highlighted above by me) here implies the compiler is free not to do it.
So is this:
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.
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.
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 ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With