ChatGPT解决这个技术问题 Extra ChatGPT

Can the compiler optimize from heap to stack allocation?

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?

No, that goes too far. Growing stack usage is a big deal, they did name a popular programming web site after it.
Clang does optimize this iff it can inline the function that's called (+ some conditions on the function body probably). godbolt.org/g/hnAMTZ
from the link mentioned by tobi303, things have changed since c++14, see [expr.new]; from c++14 on, the compiler can store Foo in the stack as long as it can prove the same behaviour (eg. nothing is thrown in do_something)

g
geza

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.


The question is if the allocation can be on stack even if 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.
@SHR: I've put emphasis on "storage is instead provided by the implementation". It can be anything, even the stack.
The background to these changes is discussed in my answer to Is the compiler allowed to optimize out heap memory allocations?
l
lorro

These are not equivalent. f.do_something() might throw, in which case the first object remains in memory, the second gets destructed.


Worth noting that declaring the function noexcept does not help the optimizers of gcc and clang, but showing clang the function body does. There is probably more to this.
@BaummitAugen If you're asking "why do compilers not perform this optimization" I think there is indeed more to it: when someone writes a new-expression they want dynamic allocation. If they wanted stack allocation they would have written 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
D
Daniel Jour

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: Foos might need to reside in some special memory region (like memory mapped IO) to function properly.