[英]When is a C++ class instantiated on the stack?
我想澄清一下在堆棧上實例化類時會發生什么。
在堆上實例化 C++ 類時:
MyClass *myclass = new MyClass();
創建一個 MyClass 類型的指針,並在同一行上通過“new MyClass();”實例化該類。 像這樣拉伸它:
MyClass *myclass;
myclass = new MyClass();
如果我沒記錯的話,會在第一行創建一個指針,然后在第二行為實例分配內存,並將指向實例地址的指針分配給 myclass。
這是否意味着當一個類以這種方式在堆棧上實例化時:
MyClass myclass = MyClass();
它創建了兩次?
“堆棧”和“堆”在 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
此外,將復制構造函數設為私有會導致編譯器錯誤。
在使用指針的情況下,第一行就像你說的那樣聲明一個指針。 帶有 new 的行不僅僅分配內存,它還將調用要調用的 MyClass 的默認構造函數。 您可以通過以下方式在一行中做到這一點:
MyClass * myClass = new MyClass;
MyClass 后面的括號在不帶參數構造時是可選的。
該對象不會在堆棧上創建,並且會一直存在,直到對指針調用 delete 為止。 如果這永遠不會發生,您將有泄漏。
在MyClass myClass = MyClass();
的情況下MyClass myClass = MyClass();
該類將創建兩次,首先使用默認構造函數,然后使用復制構造函數。 編譯器可能會將其優化為單個構造,但您實際上應該只初始化:
MyClass myClass;
請注意,您不能在聲明中使用括號,否則它將聲明一個函數而不是類的實例。
在這種情況下,將在堆棧上創建一個實例。 您的方法可能會在復制構造的過程中創建一個“臨時”,它是一種堆棧變量。 您可以將臨時對象視為由構造函數“返回”並返回值,這是一個棘手的區域,通常是自動的並使用堆棧空間。
就標准而言,沒有堆棧和堆的概念。 但是,我所知道的所有 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()
) 創建的臨時對象(沒有名稱的對象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
resp.)並且永遠不會“命中”堆棧的。 唯一的堆棧操作是pushq
和popq
(我不知道它們實際上保存/恢復了什么。
我的帖子只是為了完成 Luchian Grigore 並解決 nabulke 的問題
事實上, MyClass myclass = MyClass();
不調用賦值運算符在 C++96 規范中說明,在第 12.6.1.1 段( http://www.csci.csusb.edu/dick/c++std/cd2/special.html )。 該行說:“可以使用 = 形式的初始化將單個賦值表達式指定為初始化程序。”
我希望這有幫助
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.