簡體   English   中英

何時在堆棧上實例化 C++ 類?

[英]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 (取決於編譯器是否省略了副本),臨時對象的存儲不是由標准解決的......他們通常使用堆棧。


最后一句話:這是否最終實際使用了堆棧上的一些空間是另一回事。

  • 編譯器可能完全不需要對象
  • 變量可以存儲在堆棧或寄存器中(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 resp.)並且永遠不會“命中”堆棧的。 唯一的堆棧操作是pushqpopq (我不知道它們實際上保存/恢復了什么。

我的帖子只是為了完成 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM