As far as compiler optimizations go, is it legal and/or possible to change a heap allocation to a stack allocation? Or would that break the as-if rule?
For example, say this is the original version of the code
{
Foo* f = new Foo();
f->do_something();
delete f;
}
Would a compiler be able to change this to the following
{
Foo f{};
f.do_something();
}
I wouldn't think so, because that would have implications if the original version was relying on things like custom allocators. Does the standard say anything specifically about this?
Yes, it's legal. expr.new/10
of C++14:
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.
expr.delete/7
:
If the value of the operand of the delete-expression is not a null pointer value, then: — If the allocation call for the new-expression for the object to be deleted was not omitted and the allocation was not extended (5.3.4), the delete-expression shall call a deallocation function (3.7.4.2). The value returned from the allocation call of the new-expression shall be passed as the first argument to the deallocation function. — Otherwise, if the allocation was extended or was provided by extending the allocation of another new- expression, and the delete-expression for every other pointer value produced by a new-expression that had storage provided by the extended new-expression has been evaluated, the delete-expression shall call a deallocation function. The value returned from the allocation call of the extended new-expression shall be passed as the first argument to the deallocation function. — Otherwise, the delete-expression will not call a deallocation function (3.7.4.2).
So, in summary, it's legal to replace new
and delete
with something implementation defined, like using the stack instead of heap.
Note: As Massimiliano Janes comments, the compiler could not stick exactly to this transformation for your sample, if do_something
throws: the compiler should omit destructor call of f
in this case (while your transformed sample does call the destructor in this case). But other than that, it is free to put f
into the stack.
These are not equivalent. f.do_something()
might throw, in which case the first object remains in memory, the second gets destructed.
noexcept
does not help the optimizers of gcc and clang, but showing clang the function body does. There is probably more to this.
Foo f{}
. There are valid reasons for this and the compiler cannot know, e.g. perhaps they are running under valgrind and want to track all heap usages, or perhaps they are debugging a heap fragmentation problem. The compiler has to strike a balance between permitted optimizations, and what coders really want. The compiler should be a friend, not a foe
I'd like to point out something IMO not stressed enough in the other answers:
struct Foo {
static void * operator new(std::size_t count) {
std::cout << "Hey ho!" << std::endl;
return ::operator new(count);
}
};
An allocation new Foo()
cannot generally be replaced, because:
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.
Thus, like in the Foo
example above, the Foo::operator new
needs to be called. Omitting this call would change the observable behavior of the program.
Real world example: Foo
s might need to reside in some special memory region (like memory mapped IO) to function properly.
Success story sharing
new
used. If I understand correctly it will always be dynamic memory and never on the stack. the given paragraph say that the size of allocation can be extended to a larger block of dynamic memory, or use previous extention of dynamic memory.