2

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!

codepoet
  • 137
  • 1
  • 4
  • 1
    "local looking class object by value from function doesn't necessarily copy any data" thats not correct. The question is based on false premises. To answer it in details you need to show what `...` is – 463035818_is_not_an_ai Jul 22 '23 at 09:31
  • 2
    When you say that there's no copying made, perhaps you're thinking about [*copy elision*](https://en.cppreference.com/w/cpp/language/copy_elision)? – Some programmer dude Jul 22 '23 at 09:34
  • 1
    _"does compiler automatically refactor the code to insert delete"_ - No, that won't happen. – Ted Lyngmo Jul 22 '23 at 09:35
  • @463035818_is_not_an_ai Check the link at the bottom of post. It's points to an accepted answer of question that led me to ask this. The function a here is pretty much stamped from there. If it is a false premise, that post is false too. If your point is that "it depends" when such optimization is performed or not, you are welcome to post both cases in an answer. – codepoet Jul 22 '23 at 09:39
  • 2
    Heap allocation in C++ happens manually, not automatically. And a return value does not always get copied. There is no contradiction between these two statements (not “suggestions”). – Konrad Rudolph Jul 22 '23 at 09:39
  • no that post is not false. You only took one details it says out of context which makes it somewhat wrong. You focus too much on the "not copied" part but ignore what it means that the move constructor is called. The move constructor does copy members when they need to be copied (but it moves them when they can be moved). "nothing is copied" is too simplified and wrong – 463035818_is_not_an_ai Jul 22 '23 at 09:41
  • @463035818_is_not_an_ai There are other posts I have seen which suggest that return by value can get optimized such that it is neither copied nor moved. That's why the question asks if the local looking object is promoted to the stack frame of calling function? – codepoet Jul 22 '23 at 09:44
  • optimizations do not break code. Optimizations do the same thing effectively but skip the unnecessary parts. – 463035818_is_not_an_ai Jul 22 '23 at 09:45
  • i first misunderstood the question. Anyhow, I still think it requires the definition of `Foo` to get a good answer. – 463035818_is_not_an_ai Jul 22 '23 at 09:46
  • @463035818_is_not_an_ai "Optimizations do the same thing effectively" that's subjective. I have come across posts which suggest that the side effects are not guaranteed to be the same across compilers. So they don't do "same thing"... – codepoet Jul 22 '23 at 09:46
  • _"Did it get promoted to the stack frame of calling function?"_ More or less, yes. See [this answer of mine](https://stackoverflow.com/questions/48955310/how-does-c-abi-deal-with-rvo-and-nrvo/48955636#48955636) for a bit more detail on how (N)RVO/copy elision works. – Miles Budnek Jul 22 '23 at 09:47
  • no thats not subjective. The only case where C++ allows optimizations to have ovservable effect is when a copy can be elided. And the only observable difference is side effects of the copy, which is actually unusual – 463035818_is_not_an_ai Jul 22 '23 at 09:47
  • @463035818_is_not_an_ai Also, you are welcome to show example and counter example where definition of Foo changes the fact whether optimization is applied or not. – codepoet Jul 22 '23 at 09:48
  • `Foo(Foo&&) = delete;` – 463035818_is_not_an_ai Jul 22 '23 at 09:50

3 Answers3

5

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
2

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.

anatolyg
  • 26,506
  • 9
  • 60
  • 134
0

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.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207