C++ return by value class objects's memory whereabouts in wake of optimizations

85 views Asked by At

Let's say there is a user defined class Foo. Some posts suggest that a C++ class object is "never" allocated on heap unless allocated with new. But! on the other hand there are posts that suggest that returning a local looking class object by value from function doesn't necessarily copy any data. So! where was the data of such an object stored in first place? Is it still stack? Did it get promoted to the stack frame of calling function?

class Foo {
...
}

Foo a(int x) {
  Foo result;
  doabc(x, result);
  return result;
}

Foo b(int x) {
  Foo result = a(x);
  doxyz(x,result);
  return result;
}

int main() {
    int x;
    cin >> x;
    Foo result = b(x);
    dosomethingelse(result);
    cout << result;
}

If Foo result of from a is not copied by value, where is it allocated? Heap or stack? If on heap, does compiler automatically refactor the code to insert delete? If on stack which stack frame would it live on? b's? Here's the post that makes me wonder: https://stackoverflow.com/a/17473874/15239054. Thanks!

3

There are 3 answers

0
n. m. could be an AI On

There are no heap or stack in the C++ language. These are properties of an implementation, not the language. An implementation is free to do whatever it wants as long as the resulting program does what the standard says it should do. This includes allocating storage for automatic variables or temporaries on the heap.

The "normal" implementations don't do that though. Both automatic variables and temporaries are allocated on the stack or in the registers.

In your example, all variables named result have automatic storage duration, which usually means they are allocated on the stack (inasmuch the implementation has such thing as "the stack"—the language is silent about it). Copying from one variable to another does not normally involve any third variable or temporary that needs to be allocated somewhere. This includes the case of returning a value from a function.

An object may own resources such as memory, that need to be allocated on the free store ("heap" in terms of implementations that have a heap). If this is the case, then the copy constructor or the copy assignment operator take care of copying, and allocate stuff wherever needed. But this is about the resources owned by the object, not the object itself.

The question you link is about avoidance of copying, specifically avoidance of copying of the owned resources. This has little to do with any of the above.

An implementation is allowed, and sometimes required, to elide copies. Copy elision is one way to avoid copying. Here is an example:

#include <iostream>

struct Foo
{
    Foo() {}
    Foo(const Foo&) { std::cout << "Copied by ctor\n"; };
    Foo& operator=(const Foo&) { std::cout << "Copied by assignment\n"; return *this; };
};

Foo func()
{
    Foo foo;
    return foo;
}

int main()
{
    Foo foo (func());
}

Live demo. This example shows both mandatory and optional copy elision.

In C++14 and below, there are two cases of optional copy elision. In C++17 and above, there is one case of optional and one case of mandatory copy elision (a new feature in C++17, officially called "temporary materialization"). GCC performs optional copy elision by default but it can be disabled with -fno-elide-constructors. It is an optimisation allowed by the standard. Mandatory copy elision cannot be disabled because the standard mandates it.

Another way to avoid copying is to use moving instead of copying when possible. Move semantics is a separate story which is beyond the scope of this answer.

0
anatolyg On

You can't have a general answer to such a question. If your code is simple enough, the storage is not in the stack, but in registers.

If your function returns by value, the storage is either on the stack or in registers — depends on the size of your object (also on the details of your compiler).

It may be meaningless to talk about "where the object is" — the compiler might optimize it out of existence. If you disassemble your compiled code, you may discover where your objects are stored, but that may require some imagination: for example, half of the object's data may be on the stack, and the other half in registers. Or half in registers, and half optimized out.

0
HolyBlackCat On

Short answer:

Assuming that no copy is made (compiler performed NRVO), the caller will allocate memory for the object on the stack, and pass a pointer to it to the callee, which will call a constructor for the class on that location.

This, of course, assumes no optimizations (except for NRVO itself).

E.g. a sufficiently small and simple (no non-trivial copy/move operations?) class might be returned in a register.