什么时候在堆栈上实例化C ++类?

时间:2012-01-06 07:45:03

标签: c++ memory-management

我想澄清一下在堆栈上实例化类时会发生什么。

在堆上实例化C ++类时:

MyClass *myclass = new MyClass();

创建一个类型为MyClass的指针,并通过“new MyClass();”在同一行上实例化该类。像这样伸展出来:

MyClass *myclass;
myclass = new MyClass();

如果我没弄错的话,在第一行创建指针,然后在第二行为实例分配内存,并将指向实例地址的指针分配给myclass。

这是否意味着当一个类以这种方式在堆栈上实例化时:

MyClass myclass = MyClass();

它创建了两次?

4 个答案:

答案 0 :(得分:7)

“Stack”和“heap”在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选项-fno-elide-constructors可以防止出现这种情况):

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

此外,将复制构造函数设为私有将导致编译器错误。

答案 1 :(得分:6)

在使用指针的情况下,第一行只是声明一个指针。使用new的行不只是分配内存,它还将调用要调用的MyClass的默认构造函数。您可以通过以下方式在一行中完成此操作:

MyClass * myClass = new MyClass;

MyClass之后的括号在没有参数构造时是可选的。

在堆栈上不会创建对象,并且在指针上调用delete之前它将一直存在。如果这种情况没有发生,你就会有泄漏。

在案件中 MyClass myClass = MyClass();

该类将创建两次,首先使用默认构造函数,然后使用复制构造函数。编译器可能会将其优化为单个构造,但您应该只是初始化:

MyClass myClass;

请注意,您不得在声明中使用括号,否则它将声明一个函数而不是该类的实例。

在这种情况下,将在堆栈上创建一个实例。在复制构造的过程中,您的方法可能会创建一个“临时”,它是一种堆栈变量。您可以将临时视为构造函数“返回”并返回值,这是一个棘手的区域,通常是自动的并使用堆栈空间。

答案 2 :(得分:3)

就标准而言,没有堆栈和堆的概念。但是,我所知道的所有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(取决于编译器是否省略了副本)或者没有),标准没有解决临时存储...他们通常使用堆栈。


最后一句话:这最终是否实际上在堆栈上使用了一些空间是另一回事。

  • 编译器可能完全忽略了对象的需要
  • 变量可以存储在堆栈中或寄存器中(特定于CPU的“插槽”)

行动中:

// 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并且永远不会“命中”堆栈的。唯一的堆栈操作是pushqpopq(我不知道他们实际保存/恢复了什么。

答案 3 :(得分:0)

我的帖子只是为了完成Luchian Grigore的并解决了nabulke的问题

MyClass myclass = MyClass();未调用赋值运算符的事实在C ++ 96规范的第12.6.1.1节(http://www.csci.csusb.edu/dick/c++std/cd2/special.html)中说明。该行说:“可以使用=初始化形式将单个赋值表达式指定为初始化程序。”

我希望这会有所帮助