我想澄清一下在堆栈上实例化类时会发生什么.
在堆上实例化C++类时:
MyClass *myclass = new MyClass();
Run Code Online (Sandbox Code Playgroud)
创建一个类型为MyClass的指针,并通过"new MyClass();"在同一行上实例化该类.像这样伸展它:
MyClass *myclass;
myclass = new MyClass();
Run Code Online (Sandbox Code Playgroud)
如果我没弄错的话,在第1行创建一个指针,然后在第二行为实例分配内存,并将指向实例地址的指针分配给myclass.
这是否意味着当一个类以这种方式在堆栈上实例化时:
MyClass myclass = MyClass();
Run Code Online (Sandbox Code Playgroud)
它创建了两次?
"堆栈"和"堆"在C++中没有定义,不需要在任何地方分配内存.
以你为例:
MyClass myclass = MyClass();
Run Code Online (Sandbox Code Playgroud)
您正在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();
}
Run Code Online (Sandbox Code Playgroud)
如果副本被删除,您将看到:
ctor of 1
dtor of 1
Run Code Online (Sandbox Code Playgroud)
否则(gcc选项-fno-elide-constructors,以防止出现省略):
ctor of 1
copy ctor of 2
dtor of 1
dtor of 2
Run Code Online (Sandbox Code Playgroud)
此外,将复制构造函数设置为私有将导致编译器错误.
在使用指针的情况下,第一行只是声明一个指针.使用new的行不只是分配内存,它还将调用要调用的MyClass的默认构造函数.您可以通过以下方式执行以下操作:
MyClass * myClass = new MyClass;
MyClass之后的括号在没有参数的情况下构造时是可选的.
该对象不会在堆栈上创建,并且将一直存在,直到在指针上调用delete.如果这种情况没有发生,你就会有泄漏.
如果是
MyClass myClass = MyClass();
该类将创建两次,首先使用默认构造函数,然后使用复制构造函数.编译器可能会将其优化为单个构造,但您应该只是初始化:
MyClass myClass;
请注意,您不得在声明中使用括号,否则它将声明一个函数而不是该类的实例.
在这种情况下,将在堆栈上创建一个实例.在复制构造的过程中,您的方法可能会创建一个"临时",它是一种堆栈变量.您可以将临时视为由构造函数"返回"并返回值,这是一个棘手的区域,通常是自动的并使用堆栈空间.
就标准而言,没有堆栈和堆的概念。但是,我所知道的所有 C++ 实现都将概念“自动存储持续时间”和“动态存储”分别映射到堆栈 (*) 和堆中。
(*) 如 所述@MooingDuck,这仅适用于函数变量。全局变量和静态变量(可能)具有自动存储持续时间,但它们不在堆栈中。
现在清除了:
new存储在堆中(并返回它们的地址)举个例子,更直观一点:
void f0() {
Class* c = new Class();
}
void f1() {
Class* c = 0;
c = new Class();
}
Run Code Online (Sandbox Code Playgroud)
这里c(类型Class*)存储在堆栈上并指向存储在堆上的对象(类型Class)
void f2() {
Class c = Class();
}
void f3() {
Class c;
}
Run Code Online (Sandbox Code Playgroud)
这里c存储在堆栈中。在f2有可能是临时的(没有名字的对象)由表达式创建Class(),然后复制到c(取决于编译器elides复制或没有),临时对象的存储是没有解决的标准......他们通常使用的堆栈尽管。
最后一句话:这是否最终实际使用了堆栈上的一些空间是另一回事。
在行动:
// 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);
}
Run Code Online (Sandbox Code Playgroud)
编译器(使用 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
}
Run Code Online (Sandbox Code Playgroud)
请注意如何:1. 类已被删除,2. 调用foo已被删除,3 a. 甚至没有出现。转换回 C++,我们得到:
#include <cstdio>
int main() {
printf("%d", 1);
}
Run Code Online (Sandbox Code Playgroud)
如果我们生成程序集(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
Run Code Online (Sandbox Code Playgroud)
请注意常量($1和$.L.str)是如何被推入寄存器(%esi和%esi响应)并且永远不会“命中”堆栈的。唯一的堆栈操作是pushq和popq(我不知道它们实际上保存/恢复了什么。
| 归档时间: |
|
| 查看次数: |
7599 次 |
| 最近记录: |