user336063
user336063

Reputation:

When is a C++ class instantiated on the stack?

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?

Upvotes: 5

Views: 18467

Answers (4)

Matthieu M.
Matthieu M.

Reputation: 299810

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.

Upvotes: 6

qdii
qdii

Reputation: 12963

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 

Upvotes: 0

Pubby
Pubby

Reputation: 53047

"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.

Upvotes: 7

CashCow
CashCow

Reputation: 31435

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.

Upvotes: 6

Related Questions