4

I would like some clarification on what happens when a class is instantiated on the stack.

When a C++ class is instantiated on the heap:

MyClass *myclass = new MyClass();

a pointer is created of type MyClass and the class is instantiated on the same line by "new MyClass();". Stretching it out like this:

MyClass *myclass;
myclass = new MyClass();

If I'm not mistaken, a pointer is created on the 1st line and then memory is allocated for the instance on the 2nd line and a pointer to the address of the instance is assigned to myclass.

Does this mean that when a class is instantiated on the stack this way:

MyClass myclass = MyClass();

that it's created twice?

4 Answers4

7

"Stack" and "heap" have no definition in C++, memory is not required to be allocated anywhere.

With you example here:

MyClass myclass = MyClass();

You are initializing myclass via copy constructor to the temporary object. myclass (and the temporary) has automatic storage which you can consider being allocated on "the stack" if that makes you happy.

Copy elision is allowed in this case, essentially optimizing it to MyClass myClass, however note that it cannot always do this, such as when the copy constructor is private.

Here is an example that you can test:

struct obj {
  static int c;
  int myc;
  obj() : myc(c++) {
    std::cout << "ctor of " << myc << '\n';
  }
  obj(const obj&) : myc(c++){
    std::cout << "copy ctor of " << myc << '\n';
  }
  ~obj() {
    std::cout << "dtor of " << myc << '\n';
  }
};
int obj::c = 1;

int main(int argc, char** argv)
{
  obj x = obj();
}

If the copy is elided you will see:

ctor of 1
dtor of 1

Otherwise (gcc option -fno-elide-constructors to prevent the elision happening):

ctor of 1
copy ctor of 2
dtor of 1
dtor of 2

Additionally, making the copy constructor private will give a compiler error.

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • Sorry, but why are you saying that stack and heap have no definition in C++? That's not true. How's then possible that you crash a program by doing recursion by running out of memory? – Santiago Aug 02 '20 at 16:57
  • 2
    @Santiago Most compilers implement automatic storage using the hardware stack, but that isn't a property of C++, but rather a property of specific compilers. And not all compilers do the same thing - some allocate local variables into global storage, for example. That's why C++ itself isn't defined in terms of stacks/heaps. – Pubby Aug 02 '20 at 17:34
  • Would the same apply to C? – Santiago Aug 02 '20 at 17:50
  • Yeah C is the same. – Pubby Aug 02 '20 at 17:53
6

In the case of using the pointer, the first line just declares a pointer as you say. the line with new does more than just allocate memory, it will also invoke the default constructor of MyClass to be called. You can do that in one line through:

MyClass * myClass = new MyClass;

The parentheses after MyClass are optional when it is constructed with no parameters.

The object would not be created on the stack and will exist until delete is called on the pointer. If that does not ever happen you will have a leak.

In the case of MyClass myClass = MyClass();

The class would be created twice, firstly with the default constructor and then with the copy-constructor. The compiler might optimise it away to a single construction, but you should really just initialise:

MyClass myClass;

Note here that you must not use parentheses in the declaration or it will declare a function and not an instance of the class.

In this case one instance will be created on the stack. Your method may create a "temporary" which is a sort-of stack variable, on the way to being copy-constructed. You can think of the temporary as being "returned" by the constructor and return values, which is a tricky sort of area, are in general automatic and use stack space.

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • Even with the edit, this answer is wrong. No temporary gets created. – Luchian Grigore Jan 06 '12 at 08:05
  • 1
    The parentheses After the class mentioned in your answer are optional, but if used can lead to [value initialization](http://stackoverflow.com/q/1613341/220636), correct? – nabulke Jan 06 '12 at 08:09
  • I changed my vote. But that's only if you consider automatic storage to be the stack. The standard (#12.6.1) only talks about initialization of instances, not where the temporaries are stored. But, in loose terms, you're right. +1 – Luchian Grigore Jan 06 '12 at 08:14
  • 2
    When people refer to the stack and the heap they generally mean automatic and free storage. Of course an "automatic" member variable of an object created in free storage (i.e. with new) will certainly not physically be on the stack. Technically MyClass myClass = MyClass() should invoke a default constructor and a copy constructor as they must both be accessible, but in reality the copy is likely to never take place. One thing it definitely does not invoke is assignment. – CashCow Jan 06 '12 at 11:28
  • @CashCow, "One thing it definitely does not invoke is assignment." In hindsight, I suppose that's obvious, but I'm glad you made it explicit. Does that mean that `type_name variable_name = ...` will *never* invoke assignment? And does it means `variable_name = ...` will *always* invoke assignment? (Isn't there an exception in the latter case involving [RVO](http://en.wikipedia.org/wiki/Return_value_optimization)? – Aaron McDaid Jan 08 '12 at 00:57
  • If instantiation on stack can be done by `MyClass myClass;` and if `MyClass myClass();` would invoke a function, then how would I pass arguments to the constructor of MyClass when allocating it on the stack? – Nav Feb 22 '20 at 04:47
  • MyClass myClass( a, b, c ); would not invoke a function unless a, b and c are types. The one to beware of is Foo foo( Bar() ); which appears to be constructing a Foo with a default-constructed Bar but does not. – CashCow Feb 28 '20 at 12:03
5

As far as the Standard is concerned there is no notion of stack and heap. However, all C++ implementation that I know of will map the concepts "automatic storage duration" and "dynamic storage" into stack (*) and heap respectively.

(*) As remarked by @MooingDuck, this is only true for function variables. Globals and static variables (probably) have automatic storage duration, yet they are not on the stack.


Now this is cleared:

  • variables in a function body are stored on the stack
  • objects created by new are stored in the heap (and their address returned)

With examples, to be a bit more visual:

void f0() {
  Class* c = new Class();
}

void f1() {
  Class* c = 0;
  c = new Class();
}

Here c (of type Class*) is stored on the stack and points to an object (of type Class) that is stored on the heap

void f2() {
  Class c = Class();
}

void f3() {
  Class c;
}

Here c is stored on the stack. In f2 there might be a temporary (object without name) created by the expression Class() and then copied into c (depending on whether the compiler elides the copy or not), the storage of temporaries is not addressed by the Standard... they commonly use the stack though.


A final word: whether this ends up actually using some space on the stack or not is another matter.

  • The compiler may completely elide the need for the object
  • Variables may be stored either on the stack or in registers (CPU specific "slots")

In action:

// Simple test.cpp
#include <cstdio>

struct Class { void foo(int& a) { a += 1; } };

int main() {
  Class c;

  int a = 0;

  c.foo(a);

  printf("%d", a);
}

The compiler (using Clang/LLVM... slightly reworked) generates:

@.str = private unnamed_addr constant [3 x i8] c"%d\00", align 1

define i32 @main() nounwind uwtable {
  %1 = tail call i32 (i8*, ...)* @printf(@.str, i32 1)
  ret i32 0
}

Note how: 1. The class has been removed, 2. The call to foo has been removed, 3. a does not even appear. Translated back to C++ we get:

#include <cstdio>

int main() {
  printf("%d", 1);
}

And if we generate the assembly (64-bit X86):

main:                        # @main
pushq   %rax             # save content of 'rax' on the stack
movl    $.L.str, %edi    # move address of "%d" into the 'edi' register
movl    $1, %esi         # move 1 into the 'esi' register
xorb    %al, %al         # --
callq   printf           # call printf, it'll look up its parameters in registers
xorl    %eax, %eax       # --
popq    %rdx             # restore content from stack to 'rdx'
ret                      # return

Note how constants ($1 and $.L.str) are pushed into registers (%esi and %esi resp.) and never "hit" the stack. The only stack manipulations are pushq and popq (and I have no idea what they actually save/restore.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I believe static variables are also considered automatic, but are not on the stack – Mooing Duck Jan 07 '12 at 04:30
  • Although I did not accept your answer in the end, I wanted to let you know that I had a hard time choosing. I really appreciated how you took me down the rabbit hole a little farther than the rest. In the end, Pubby nailed it with the -fno-eilde-constructor case to show both possibilities. –  Jan 08 '12 at 00:58
  • @Sosukodo: no worry :) I am glad you appreciated the answer. – Matthieu M. Jan 09 '12 at 07:53
  • @MooingDuck: Excellent remark. Indeed I think that all globals/static have automatic storage (since one does not need to explicitly "free" them). I'll make a caveat. – Matthieu M. Jan 09 '12 at 07:54
0

My post is just to complete Luchian Grigore’s and address a nabulke’s question

The fact that MyClass myclass = MyClass(); does not call the assignment operator is stated in the C++96 norm, in the paragraph 12.6.1.1 ( http://www.csci.csusb.edu/dick/c++std/cd2/special.html ). The line says : “a single assignment-expression can be specified as an initializer using the = form of initialization.”

I hope this helps 

qdii
  • 12,505
  • 10
  • 59
  • 116
  • Actually, the full quote is necessary. Essentially: `Class c = Class()` might invoke first the default constructor and then the copy constructor (unless copy is elided) while `Class c = 3` where `Class(int)` exists will invoke the constructor directly. The two cases should not be aggregated. – Matthieu M. Jan 06 '12 at 08:46
  • @MatthieuM. `Class c = 3;` will first create a temporary object by converting `3` to `Class`, then copy construct `c` using this temporary object. The copy may be elided, but this is an optimization. – James Kanze Jan 06 '12 at 10:07