Когда создается класс C ++ в стеке? - PullRequest
1 голос
/ 06 января 2012

Я хотел бы получить некоторые пояснения о том, что происходит, когда экземпляр класса создается в стеке.

Когда экземпляр класса C ++ создается в куче:

MyClass *myclass = new MyClass();

создается указатель изТип MyClass и экземпляр класса в той же строке "new MyClass ();".Вытянув его так:

MyClass *myclass;
myclass = new MyClass();

Если я не ошибаюсь, указатель создается в 1-й строке, а затем выделяется память для экземпляра во 2-й строке и указатель на адрес адресаэкземпляр присваивается myclass.

Означает ли это, что когда экземпляр класса создается в стеке следующим образом:

MyClass myclass = MyClass();

, что он создается дважды?

Ответы [ 4 ]

7 голосов
/ 06 января 2012

«Стеки» и «куча» не имеют определения в C ++, память нигде не требуется выделять.

С вами пример здесь:

MyClass myclass = MyClass();

Вы инициализируете myclass через конструктор копирования во временный объект. myclass (и временное) имеет автоматическое хранилище, которое вы можете рассмотреть как разместить в «стеке», если это вас радует.

В этом случае разрешено копирование, что существенно оптимизирует его до MyClass myClass, однако обратите внимание, что это не всегда возможно, например, когда конструктор копирования является закрытым.

Вот пример, который вы можете проверить:

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();
}

Если копия будет удалена, вы увидите:

ctor of 1
dtor of 1

В противном случае (gcc option -fno-elide-constructors для предотвращения исключения):

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

Кроме того, закрытие конструктора копирования приведет к ошибке компилятора.

6 голосов
/ 06 января 2012

В случае использования указателя, первая строка просто объявляет указатель, как вы говорите.строка с новым делает больше, чем просто выделяет память, она также вызывает конструктор по умолчанию MyClass для вызова.Вы можете сделать это в одной строке через:

MyClass * myClass = new MyClass;

Скобки после MyClass являются необязательными, если они построены без параметров.

Объект не будет создан в стеке и будет существовать, пока не будет вызвано удаление указателя.Если этого не произойдет, у вас будет утечка.

В случае MyClass myClass = MyClass();

Класс будет создан дважды, сначала с конструктором по умолчанию, а затем с конструктором копирования,Компилятор может оптимизировать его до единой конструкции, но вы должны просто инициализировать:

MyClass myClass;

Обратите внимание, что вы не должны использовать круглые скобки в объявлении, иначе он объявит функциюа не экземпляр класса.

В этом случае в стеке будет создан один экземпляр.Ваш метод может создать «временную» переменную типа стека на пути к созданию копии.Вы можете думать о временном объекте как о «возвращаемом» конструктором, а возвращаемые значения, которые являются хитрой областью, в общем случае являются автоматическими и используют пространство стека.

3 голосов
/ 06 января 2012

Что касается Стандарта, то здесь нет понятия стека и кучи.Тем не менее, все известные мне реализации C ++ отображают понятия «продолжительность автоматического хранения» и «динамическое хранение» в стек (*) и кучу соответственно.

(*) Как отмечено @MooingDuck, этоверно только для переменных функций.Глобальные и статические переменные (вероятно) имеют автоматическую продолжительность хранения, но они не находятся в стеке.


Теперь это очищено:

  • переменные в теле функции сохраняютсяв стеке
  • объекты, созданные new, хранятся в куче (и их адрес возвращается)

С примерами, чтобы быть немного более наглядным:

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

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

Здесь c (типа Class*) хранится в стеке и указывает на объект (типа Class), который хранится в куче

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

void f3() {
  Class c;
}

Здесь c хранится в стеке.В f2 может быть временным (объект без имени), созданным выражением Class() и затем скопированным в c (в зависимости от того, исключает ли компилятор копию или нет), хранилищеВременные значения не рассматриваются Стандартом ... хотя они обычно используют стек.


Последнее слово: заканчивается ли это тем, что фактически используется какое-то место в стеке или нет, это другой вопрос.

  • Компилятор может полностью исключить необходимость в объекте
  • Переменные могут храниться либо в стеке, либо в регистрах (специфичные для процессора "слоты")

Inдействие:

// 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);
}

Компилятор (использующий Clang / LLVM ... слегка переработанный) генерирует:

@.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
}

Обратите внимание, как: 1. Класс был удален, 2. Вызовдо foo был удален, 3. a даже не появляется.Переведенный обратно в C ++ мы получим:

#include <cstdio>

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

И если мы сгенерируем сборку (64-битный 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

Обратите внимание, как константы ($1 и $.L.str)помещаются в регистры (%esi и %esi соответственно) и никогда не "попадают" в стек.Единственные манипуляции со стеком pushq и popq (и я понятия не имею, что они на самом деле сохраняют / восстанавливают.

0 голосов
/ 06 января 2012

Мой пост только для того, чтобы завершить задание Лучиана Григоре и ответить на вопрос Набулька

Тот факт, что MyClass myclass = MyClass(); не вызывает оператор присваивания, указан в норме C ++ 96, в пункте 12.6.1.1(http://www.csci.csusb.edu/dick/c++std/cd2/special.html).Строка гласит: «В качестве инициализатора можно указать одно выражение-присваивание с использованием формы инициализации =».

Надеюсь, это поможет

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...