我想澄清一下在堆栈上实例化类时会发生什么。
在堆上实例化C ++类时:
MyClass *myclass = new MyClass();
创建一个类型为MyClass的指针,并通过“new MyClass();”在同一行上实例化该类。像这样伸展出来:
MyClass *myclass;
myclass = new MyClass();
如果我没弄错的话,在第一行创建指针,然后在第二行为实例分配内存,并将指向实例地址的指针分配给myclass。
这是否意味着当一个类以这种方式在堆栈上实例化时:
MyClass myclass = MyClass();
它创建了两次?
答案 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
(取决于编译器是否省略了副本)或者没有),标准没有解决临时存储...他们通常使用堆栈。
最后一句话:这最终是否实际上在堆栈上使用了一些空间是另一回事。
行动中:
// 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
(我不知道他们实际保存/恢复了什么。
答案 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)中说明。该行说:“可以使用=初始化形式将单个赋值表达式指定为初始化程序。”
我希望这会有所帮助