Consider the following simple code that makes use of new
(I am aware there is no delete[]
, but it does not pertain to this question):
int main()
{
int* mem = new int[100];
return 0;
}
Is the compiler allowed to optimize out the new
call?
In my research, g++ (5.2.0) and Visual Studio 2015 do not optimize out the new
call, while clang (3.0+) does. All tests have been made with full optimizations enabled (-O3 for g++ and clang, Release mode for Visual Studio).
Isn't new
making a system call under the hood, making it impossible (and illegal) for a compiler to optimize that out?
EDIT: I have now excluded undefined behaviour from the program:
#include <new>
int main()
{
int* mem = new (std::nothrow) int[100];
return 0;
}
clang 3.0 does not optimize that out anymore, but later versions do.
EDIT2:
#include <new>
int main()
{
int* mem = new (std::nothrow) int[1000];
if (mem != 0)
return 1;
return 0;
}
clang always returns 1.
Heap allocation is the most flexible allocation scheme. Allocation and deallocation of memory can be done at any time and any place depending upon the user's requirement. Heap allocation is used to allocate memory to the variables dynamically and when the variables are no more used then claim it back.
It does not follow any order because it is a dynamic memory allocation and does not have any fixed pattern for allocation and deallocation of memory blocks. It is not flexible because we cannot alter the allocated memory.
The advantages of heap memory are: Lifetime. Because the programmer now controls exactly when memory is allocated, it is possible to build a data structure in memory, and return that data structure to the caller. This was never possible with local memory, which was automatically deallocated when the function exited.
When the heap becomes full, garbage is collected. During the garbage collection objects that are no longer used are cleared, thus making space for new objects.
The history seems to be that clang is following the rules laid out in N3664: Clarifying Memory Allocation which allows the compiler to optimize around memory allocations but as Nick Lewycky points out :
Shafik pointed out that seems to violate causality but N3664 started life as N3433, and I'm pretty sure we wrote the optimization first and wrote the paper afterwards anyway.
So clang implemented the optimization which later on became a proposal that was implemented as part of C++14.
The base question is whether this is a valid optimization prior to N3664
, that is a tough question. We would have to go to the as-if rule covered in the draft C++ standard section 1.9
Program execution which says(emphasis mine):
The semantic descriptions in this International Standard define a parameterized nondeterministic abstract machine. This International Standard places no requirement on the structure of conforming implementations. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below.5
where note 5
says:
This provision is sometimes called the “as-if” rule, because an implementation is free to disregard any requirement of this International Standard as long as the result is as if the requirement had been obeyed, as far as can be determined from the observable behavior of the program. For instance, an actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no side effects affecting the observable behavior of the program are produced.
Since new
could throw an exception which would have observable behavior since it would alter the return value of the program, that would seem to argue against it being allowed by the as-if rule.
Although, it could be argued it is implementation detail when to throw an exception and therefore clang could decide even in this scenario it would not cause an exception and therefore eliding the new
call would not violate the as-if rule.
It also seems valid under the as-if rule to optimize away the call to the non-throwing version as well.
But we could have a replacement global operator new in a different translation unit which could cause this to affect observable behavior, so the compiler would have to have some way a proving this was not the case, otherwise it would not be able to perform this optimization without violating the as-if rule. Previous versions of clang did indeed optimize in this case as this godbolt example shows which was provided via Casey here, taking this code:
#include <cstddef>
extern void* operator new(std::size_t n);
template<typename T>
T* create() { return new T(); }
int main() {
auto result = 0;
for (auto i = 0; i < 1000000; ++i) {
result += (create<int>() != nullptr);
}
return result;
}
and optimizing it to this:
main: # @main
movl $1000000, %eax # imm = 0xF4240
ret
This indeed seems way too aggressive but later versions do not seem to do this.
This is allowed by N3664.
An implementation is allowed to omit a call to a replaceable global allocation function (18.6.1.1, 18.6.1.2). When it does so, the storage is instead provided by the implementation or provided by extending the allocation of another new-expression.
This proposal is part of the C++14 standard, so in C++14 the compiler is allowed to optimize out a new
expression (even if it might throw).
If you take a look at the Clang implementation status it clearly states that they do implement N3664.
If you observe this behavior while compiling in C++11 or C++03 you should fill a bug.
Notice that before C++14 dynamic memory allocations are part of the observable status of the program (although I can not find a reference for that at the moment), so a conformant implementation was not allowed to apply the as-if rule in this case.
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