C++ 類大小分析
以下測試程式碼的執行環境:
Ubuntu 16.04.4 LTS
gcc version 4.8.5
x64
- 空類、單一繼承的空類、多重繼承的空類所佔空間大小為:1(位元組)。
例項在記憶體中都有一個獨一無二的地址,為了達到這個目的,編譯器往往會給一個空類隱含的加一個位元組,這樣空類在例項化後在記憶體得到了獨一無二的地址
- 一個類中,虛擬函式本身、成員函式(包括靜態與非靜態)和靜態資料成員都是不佔用類物件的儲存空間。
類中如果有虛擬函式,那麼該類物件會生成一張虛擬函式表,然後編譯器會生成一個指向該虛表的指標vptr(virtual table pointer),該指標佔用儲存空間,具體下面虛擬函式會講解,靜態資料成員存放的在靜態儲存區,跟類物件儲存空間不一樣。
- 為了優化存取效率,通常都會遵守記憶體對齊原則。
通俗的理解是,類中最長的資料成員作為對齊原則,從其內部"最寬基本型別成員"的整數倍地址開始儲存。比如類中有 char、int、float、double等元素,那麼類應該從8的整數倍開始儲存。
例如:
class AA{
public:
char i;
char j;
double k;
};
int main()
{
cout<<"size = "<<sizeof(AA)<<endl;
return 0;
}
輸出:16
該類大小等於資料大小之和,由於該類的“最寬”資料型別為double,所以按照8的整數倍進行儲存,這裡的計算方式:1+1+(6)+8 = 16.
註釋:上面計算方法中的“(6)”是記憶體補齊,編譯器自動處理的。之後的計算類似這樣都表示記憶體補齊。
注意,如果資料的擺放順序不一樣,則類的大小也是有區別的:
class AA{
public:
char i;
double j;
char k;
};
int main()
{
cout<<"size = "<<sizeof(AA)<<endl;
return 0;
}
輸出:24
類中的資料成員是按照順序進行擺放的,所以計算方式:1+(7)+8+1+(7) = 24
- 虛擬函式,有虛擬函式的類有個virtual table(虛擬函式表),裡面包含了類的所有虛擬函式,類中有個virtual table pointers,通常成為vptr指向這個virtual table。
一個類裡邊無論一個或者多個虛擬函式,都只有一個vptr,只要有一個虛擬函式,類中肯定會生成一張虛擬函式表vtable,表中儲存的就是每個虛擬函式的地址。vptr指向vtable,然後在vtable中找到對應的虛擬函式。
class AA{
public:
char i;
int j;
int k;
virtual void func();
virtual ~AA();
};
輸出:24
注意,這裡也有一個坑,由於測試環境是gcc version 4.8.5,ubuntu 系統 64位,所以虛擬函式指標vptr是8個位元組,如果系統是32位,則是4個位元組。這裡的計算方式:1+4+(3)+4+(4)+8 = 24.
- 64位和32位,資料型別大小的區別
最大的區別就是指標資料型別64位的大小是8個位元組,32位的是4個位元組
具體請參考:https://blog.csdn.net/puppet_master/article/details/50044965
- 帶有虛擬函式的單繼承
子類繼承一個帶有虛擬函式的基類,無論子類有沒有虛擬函式,或者有虛擬函式覆蓋的情況,子類也只有一個虛擬函式表。
繼承關係圖如下:參考https://www.cnblogs.com/yanqi0124/p/3829964.html
基類和派生類之間的關係:其中基類的虛擬函式f在派生類中被覆蓋了
class AA{
public:
char i;
int j;
int k;
virtual void func();
virtual ~AA();
};
class BB : public AA{
public:
int n;
virtual void func();
};
輸出:AA大小:24,BB大小:24
BB繼承AA的所有資料,其資料成員存放順序如下:
char i;
int j;
int k;
int n;
計算方法:1+4+(3)+4+4+8(vptr大小) = 24
- 多繼承,基類如何有虛擬函式,則子類會儲存一個指向基類虛表的指標。
繼承關係圖如下:參考https://www.cnblogs.com/yanqi0124/p/3829964.html
假設基類和派生類之間有如下關係:
對於子類例項中的虛擬函式表,是下面這個樣子:
假設,基類和派生類又如下關係:派生類中覆蓋了基類的虛擬函式f
下面是對於子類例項中的虛擬函式表的圖:
從上圖中可以看出,只要繼承的父類有虛擬函式,那麼子類就有一個對應的指向基類虛表的指標vptr。
class AA{
public:
int i;
virtual void funcd();
virtual ~AA();
};
class BB{
public:
int j;
virtual void func();
virtual ~BB();
};
class CC : public AA, public BB{
public:
int k;
virtual void func();
~CC();
};
輸出:
AA大小:16
BB大小:16
CC大小:32
- 虛繼承,由於涉及到虛擬函式表和虛基表,會同時增加一個(多重虛繼承下對應多個)vPtr指標指向虛擬函式表vbTable和一個vbPtr指標指向虛基表vbTable。
class AA{
public:
int i;
virtual void funcaa();
virtual ~AA();
};
class BB : virtual public AA{
public:
int j;
virtual void funcbb();
virtual ~BB();
};
class CC : virtual public AA{
public:
int k;
virtual void funccc();
~CC();
};
class DD : public BB, public CC{
virtual void funcdd();
~DD();
};
輸出:
AA大小:16
BB大小:32
CC大小:32
DD大小:48
虛繼承根據編譯器的不同,物件記憶體佈局也不一樣,再次強調,本文章裡邊所有測試環境是GCC編譯器。GCC都是將虛表指標在整個繼承關係中共享的,不共享的是指向虛基類的指標。
對於BB物件記憶體分析:
AA::vptr | //虛表指標 |
AA::i | |
BB::vbptr | //虛基類指標 |
BB::vptr | //虛表指標 |
BB::j |
由於虛擬函式指標共享,所以BB最終的大小計算:8(AA::vbptr)+(4+(4))(AA::i)+(4+(4))(BB::j)+8(vptr 共享虛擬函式指標)=32
CC跟BB是類似的計算。
DD的物件記憶體分析:
BB::vptr | //虛表指標 |
BB::vbptr | //虛基類指標 |
BB::j | |
CC::vptr | //虛表指標 |
CC::vbptr | //虛基類指標 |
CC::k | |
AA::vptr | //虛表指標 |
AA::i |
DD大小計算:8(BB::vbptr)+(4+(4))(BB::j)+8(CC::vbptr)+(4+(4))(CC::k)+(4+(4))(AA::i)+8(vptr 共享虛擬函式指標)=48
補充:如果是在VC編譯器上,則虛擬函式指標不共享。