2009-06-02 49 views
30

例如,假設我有一個類溫度:如何確定C++中對象的大小?

class Temp 
{ 
    public: 
     int function1(int foo) { return 1; } 
     void function2(int bar) { foobar = bar; } 

    private: 
     int foobar; 
}; 

當我創建類溫度的目的,我將如何計算它需要多大的空間,並在內存中是如何表示的(例如| 4個字節for foobar | 8字節for function1 | etc |)

+1

這個問題可能有點更具描述中包含「你如何確定在C++中對象的大小?」的標題。 – 2009-06-02 03:42:35

+0

Here是關於這個問題的優秀書。 – 2009-06-02 03:26:53

回答

54

對於一階近似,對象的大小是其組成數據成員大小的總和。你可以肯定它永遠不會比這個小。

更確切地說,編譯器有權在數據成員之間插入填充空間以確保每個數據成員滿足平臺的對齊要求。一些平臺對齊非常嚴格,而另一些平臺(x86)更寬容,但通過適當的對齊將顯着提高性能。所以,即使編譯器優化設置也會影響對象的大小。

繼承和虛函數會增加額外的複雜性。正如其他人所說的那樣,類的成員函數本身並不佔用「每個對象」的空間,但在該類的接口中存在虛函數通常意味着存在一個虛表,實質上是一個函數指針查找表,用於動態地解析正確的函數實現以在運行時調用。虛擬表(vtbl)通常通過存儲在每個對象中的指針來訪問。

派生類對象還包括其基類的所有數據成員。

最後,訪問說明符(public,private,protected)賦予編譯器一定的餘地幷包裝數據成員。

簡短的回答是,sizeof(myObj)或sizeof(MyClass)會一直告訴你一個對象的大小,但其結果並不總是容易預測。

15
sizeof(Temp) 

會給你大小。最有可能的是,它是4個字節(給出了大量的假設),並且僅用於整數。這些函數不會在每個對象的基礎上佔用任何空間,它們會被編譯一次,並在每次使用時由編譯器進行鏈接。

不可能準確地說出對象佈局是什麼,但是,標準沒有定義對象的二進制表示。

有幾件事情需要注意的二進制表示,像他們不一定是數據成員的字節數的總和,由於像structure padding

+2

也是由於像虛擬表指針這樣的事情。 – jrharshath 2009-06-02 03:55:30

0

This事情可能會有所幫助。

此外,類函數與其他函數一樣表示。 C++對這個函數做的唯一的神奇之處就是對函數名稱進行調整,以便在特定的類中使用一組特定的參數來唯一標識特定的函數。

4

成員函數不考慮特定類的對象的大小。對象的大小僅取決於成員變量。在包含虛函數的類中,VPTR被添加到對象佈局。所以對象的大小基本上是成員變量的大小+ VPTR的大小。有時這可能不正確,因爲編譯器試圖在DWORD邊界處定位成員變量。

8

如果您想了解對象是如何在運行時內存爲代表的詳細信息,在ABI(Application Binary Interface)規範是看的地方。你需要看看你的編譯器實現了哪個ABI;例如,GCC版本3.2及以上版本實現Itanium C++ ABI

6

方法屬於類,而不是任何特定的實例化對象。

除非有虛擬方法,否則對象的大小是其非靜態成員大小的總和,以及用於對齊的成員之間的可選填充。成員可能會按順序排列在內存中,但規範並不保證具有不同訪問規範的部分之間的順序,也不保證與超類的佈局相關的順序。

使用虛擬方法時,可能會有額外的空間用於vtable和其他RTTI信息。

在大多數平臺上,可執行代碼放在可執行文件或庫的只讀.text(或類似的名稱)部分,是從來沒有在任何地方複製。當class Temp有一個方法public: int function1(int),在Temp元數據可以有一個指向_ZN4Temp9function1Ei實際實現函數(重整名稱可能因編譯器是不同的),但可以肯定它永遠不會包含嵌入可執行代碼。

1

如果您想檢查特定結構的佈局,則offsetof(s,member)宏也可能有用。它會告訴你一個特定成員的生活從結構的基址有多遠:

struct foo { 
    char *a; 
    int b; 
}; 

// Print placement of foo's members 
void printFoo() { 
    printf("foo->a is %zu bytes into a foo\n", offsetof(struct foo, a)); 
    printf("foo->b is %zu bytes into a foo\n", offsetof(struct foo, b)); 
} 

int main() { 
    printFoo(); 
    return 0; 
} 

請問一個典型的32位機上打印:

foo->a is 0 bytes into a foo 
foo->b is 4 bytes into a foo 

而一個典型的64位計算機上,它將打印

foo->a is 0 bytes into a foo 
foo->b is 8 bytes into a foo 
-1

的類的對象的大小等於類的所有數據成員的大小的總和。例如,如果我有一個類

class student 
{ 
private: 
    char name[20]; 
    int rollno, admno; 
    float marks; 

public: 
    float tmarks, percentage; 

    void getdata(); 

    void putdata(); 
}; 

現在,如果我讓這個類的一個對象,說s1,那麼這個物體的大小爲36個字節:

[20(name)+2(rollno)+2(admno)+4(marks)+4(tmarks)+4(percentage)] 
0

有一個實用工具調用pahole(對於'Poke-A-HOLE')名義上用於研究如何填充對象佈局,但對於可視化對象大小和總體佈局非常有用。

5

我一直都在想這樣的事情,所以我決定拿出一個完整的答案。這是關於你可能期望的,它是可預測的(耶)!因此,根據以下信息,您應該能夠預測課程的大小。

使用Visual Studio Community 2017(版本15。2),在Release模式與所有的優化禁用和RTTI(Run-time Type Information)關閉,我已確定以下內容:


稍短答案:

首先:

  • 在32(x86)位中,<size of pointer> == 4字節
  • 在64(x64)位中,<size of pointer> == 8字節
  • 當我說「虛類繼承」,我的意思是比如:class ChildClass: virtual public ParentClass

現在,我的結論是:

  • 空類爲1所字節
  • 繼承的空類是仍然有1個字節
  • 具有函數的空類仍然是1個字節(?!見注意下面用於解釋)
  • 繼承與函數的空類的還是1字節
  • 添加變量爲一個空類<size of variable>字節
  • 繼承一類具有一個變量並添加另一變量是<size of variables>字節
  • 繼承一個類並覆蓋其功能增加了一個虛函數表(在結論部提供進一步的解釋),並且是<size of pointer>字節
  • 簡單地聲明一個函數VIRTUA升還增加了一個虛函數表,使它<size of pointer>字節
  • 一個空類(有或沒有成員函數)的虛擬類繼承還增加了一個虛函數表,並且使類<size of pointer>字節的非空的
  • 虛擬類繼承類還增加了一個虛函數表,但它變得有點複雜:增加<size of pointer>字節總數,包裝所有成員變量在儘可能多的<size of pointer>字節爲增量是必要的覆蓋<total size of member variables> - 是的,你沒有看錯。 ..(看我的猜測結論 ...)

注意我甚至嘗試過有函數()cout一些文本,創建一個類的實例,並調用函數;它不會改變函數類的大小(這不是優化)!我有點驚訝,但它確實有道理:成員函數不會改變,所以它們可以存儲在類本身的外部。

結論:

  • 空類是1個字節,因爲這是它在存儲器中具有存在的最低要求。 一旦添加了數據或vtable數據,雖然開始從0字節開始計數。
  • 添加一個(非虛擬)成員函數對大小沒有影響,因爲成員函數存儲在外部。
  • 聲明一個成員函數是虛擬的(即使類沒有overridded!)或覆蓋在子類中的成員函數添加所謂的"vtable" or "virtual function table",允許Dynamic Dispatch(這是真正的超級真棒雖則使用,我強烈建議使用它)。該虛擬表消耗<size of pointer>字節,將<size of pointer>字節添加到所述類。當然,這個vtable每個類只能存在一次(無論是否存在)。
  • 添加一個成員變量通過成員變量增加類的大小,無論所述成員變量是在父母或子女類(父類仍是其自身的規模雖然,當然)。
  • 虛擬類的繼承是變得複雜的唯一部分...所以...我想一個小實驗之後發生了什麼事情是:在類的大小實際上<size of pointer>字節是一次遞增,即使它不」噸需要消耗那麼多的內存,我猜,因爲它增加一個虛函數表「助手塊」每個<size of pointer>字節的內存或東西...

龍答:

我用這個代碼確定了所有這些:

#include <iostream> 

using namespace std; 

class TestA 
{ 

}; 

class TestB: public TestA 
{ 

}; 

class TestC: virtual public TestA 
{ 

}; 

class TestD 
{ 
    public: 
     int i; 
}; 

class TestE: public TestD 
{ 
    public: 
     int j; 
}; 

class TestF: virtual public TestD 
{ 
    public: 
     int j; 
}; 

class TestG 
{ 
    public: 
     void function() 
     { 

     } 
}; 

class TestH: public TestG 
{ 
    public: 
     void function() 
     { 

     } 
}; 

class TestI: virtual public TestG 
{ 
    public: 
     void function() 
     { 

     } 
}; 

class TestJ 
{ 
    public: 
     virtual void function() 
     { 

     } 
}; 

class TestK: public TestJ 
{ 
    public: 
     void function() override 
     { 

     } 
}; 

class TestL: virtual public TestJ 
{ 
    public: 
     void function() override 
     { 

     } 
}; 

void main() 
{ 
    cout << "int:\t\t" << sizeof(int) << "\n"; 
    cout << "TestA:\t\t" << sizeof(TestA) << "\t(empty class)\n"; 
    cout << "TestB:\t\t" << sizeof(TestB) << "\t(inheriting empty class)\n"; 
    cout << "TestC:\t\t" << sizeof(TestC) << "\t(virtual inheriting empty class)\n"; 
    cout << "TestD:\t\t" << sizeof(TestD) << "\t(int class)\n"; 
    cout << "TestE:\t\t" << sizeof(TestE) << "\t(inheriting int + int class)\n"; 
    cout << "TestF:\t\t" << sizeof(TestF) << "\t(virtual inheriting int + int class)\n"; 
    cout << "TestG:\t\t" << sizeof(TestG) << "\t(function class)\n"; 
    cout << "TestH:\t\t" << sizeof(TestH) << "\t(inheriting function class)\n"; 
    cout << "TestI:\t\t" << sizeof(TestI) << "\t(virtual inheriting function class)\n"; 
    cout << "TestJ:\t\t" << sizeof(TestJ) << "\t(virtual function class)\n"; 
    cout << "TestK:\t\t" << sizeof(TestK) << "\t(inheriting overriding function class)\n"; 
    cout << "TestL:\t\t" << sizeof(TestL) << "\t(virtual inheriting overriding function class)\n"; 

    cout << "\n"; 
    system("pause"); 
} 

輸出:

32(86)位:

int:   4 
TestA:   1  (empty class) 
TestB:   1  (inheriting empty class) 
TestC:   4  (virtual inheriting empty class) 
TestD:   4  (int class) 
TestE:   8  (inheriting int + int class) 
TestF:   12  (virtual inheriting int + int class) 
TestG:   1  (function class) 
TestH:   1  (inheriting function class) 
TestI:   4  (virtual inheriting function class) 
TestJ:   4  (virtual function class) 
TestK:   4  (inheriting overriding function class) 
TestL:   8  (virtual inheriting overriding function class) 

64(64)位:

int:   4 
TestA:   1  (empty class) 
TestB:   1  (inheriting empty class) 
TestC:   8  (virtual inheriting empty class) 
TestD:   4  (int class) 
TestE:   8  (inheriting int + int class) 
TestF:   24  (virtual inheriting int + int class) 
TestG:   1  (function class) 
TestH:   1  (inheriting function class) 
TestI:   8  (virtual inheriting function class) 
TestJ:   8  (virtual function class) 
TestK:   8  (inheriting overriding function class) 
TestL:   16  (virtual inheriting overriding function class) 

如果你想在多重繼承的信息,去圖它從你的混賬的自我! -.-