C++中類所佔的記憶體大小以及成員函式的儲存位置
類所佔記憶體的大小是由成員變數(靜態變數除外)決定的,虛擬函式指標和虛基類指標也屬於資料部分,成員函式是不計算在內的。
因為在編譯器處理後,成員變數和成員函式是分離的。成員函式還是以一般的函式一樣的存在。a.fun()是通過fun(a.this)來呼叫的。所謂成員函式只是在名義上是類裡的。
其實成員函式的大小不在類的物件裡面,同一個類的多個物件共享函式程式碼。
我們訪問成員函式和普通函式一樣會發生跳轉產生入棧出棧的開銷。所以這樣也就增加了一定的時間開銷,這也就是為什麼我們提倡把一些簡短的,呼叫頻率高的函式宣告為inline形式(行內函數)。C++類裡面的哪些成員函式是行內函數?
class CBase
{
};
// sizeof(CBase)=1;
為什麼空的什麼都沒有是1呢?
c++要求每個例項在記憶體中都有獨一無二的地址。空類也會被例項化,所以編譯器會給空類隱含的新增一個位元組,這樣空類例項化之後就有了獨一無二的地址了。所以空類的sizeof為1。
class CBase
{
int a;
char p;
};
// sizeof(CBase)=8;
位元組對齊的問題。int 佔4位元組
char佔一位元組,補齊3位元組
class CBase
{
public:
CBase(void);
virtual ~CBase(void);
private :
int a;
char *p;
};
// sizeof(CBase)=12
C++ 類中有虛擬函式的時候有一個指向虛擬函式的指標(vptr),在32位系統分配指標大小為4位元組。無論多少個虛擬函式,只有這一個指標,4位元組。
//注意一般的函式是沒有這個指標的,而且也不佔類的記憶體。
class CChild : public CBase
{
public:
CChild(void);
~CChild(void);
virtual void test();
private:
int b;
};
// sizeof(CChild)=16;
可見子類的大小是本身成員變數的大小加上父類的大小。
//其中有一部分是虛擬函式表的原因,一定要知道父類子類共享一個虛擬函式指標
class A {}; // sizeof(A)=1
class B{}; // sizeof(B)=1
class C:public A{
virtual void fun()=0;
}; // sizeof(C)=4
class D:public B,public C{}; // sizeof(D)=8
類D的大小更讓人疑惑吧,類D是由類B,C派生過來的,它的大小應該為二者之和5,為什麼卻是8 呢?這是因為為了提高例項在記憶體中的存取效率。類的大小往往被調整到系統的整數倍。並採取就近的法則,距離哪個最近的倍數,就是該類的大小,所以類D的大小為8個位元組。
// ====== 測試一 ======
class Test {
private:
int n;
char c;
short s;
};
Test t21;
cout << sizeof(t21) << endl;
// 執行結果:8
// ====== 測試二 ======
class Test {
public:
//建構函式
Test() {
}
//普通成員
int func0() {
return n;
}
//友元函式
friend int func1();
//常成員函式
int func2() const {
return s;
}
//行內函數
inline void func3() {
cout << "inline function" << endl;
}
//靜態成員函式
static void func4() {
cout << "static function" << endl;
}
//解構函式
~Test() {
}
private:
int n;
char c;
short s;
};
int func1() {
Test t;
return t.c;
}
Test t22;
cout << sizeof(t22) << endl;
// 執行結果:8
// ====== 測試三 ======
class Test {
public:
Test() {
}
int func0() {
return n;
}
friend int func1();
int func2() const {
return s;
}
inline void func3() {
cout << "inline function" << endl;
}
static void func4() {
cout << "static function" << endl;
}
//虛擬函式,需要一個虛擬函式指標的開銷
virtual void func5() {
cout << "virtual function" << endl;
}
~Test() {
}
private:
int n;
char c;
short s;
};
int func1() {
Test t;
return t.c;
}
Test t23;
cout << sizeof(t23) << endl;
// x86目標平臺執行結果:12;x64目標平臺下執行結果:16
因 C++中成員函式和非成員函式都是存放在程式碼區的,故類中一般成員函式、友元函式,行內函數還是靜態成員函式都不計入類的記憶體空間,測試一和測試二對比可證明這一點
測試三中,因出現了虛擬函式,故類要維護一個指向虛擬函式表的指標,分別在 x86目標平臺和x64目標平臺下編譯執行的結果可證明這一點
總結
- 空的類是會佔用記憶體空間的,而且大小是1,原因是C++要求每個例項在記憶體中都有獨一無二的地址。
- 類內部的成員變數:
普通的變數:是要佔用記憶體的,但是要注意對齊原則(這點和struct型別很相似)。
static修飾的靜態變數:不佔用內容,原因是編譯器將其放在全域性變數區。
- 類內部的成員函式:
普通函式:不佔用記憶體。
虛擬函式:要佔用4個位元組(32位系統)或8個位元組(64位系統),用來指定虛擬函式的虛擬函式表的入口地址。所以一個類的虛
函式所佔用的地址是不變的,和虛擬函式的個數是沒有關係的。
- C++編譯系統中,資料和函式是分開存放的(函式放在程式碼區;資料主要放在棧區和堆區,靜態/全域性區以及文字常量區也有),例項化不同物件時,只給資料分配空間,各個物件呼叫函式時都都跳轉到(行內函數例外)找到函式在程式碼區的入口執行,可以節省拷貝多份程式碼的空間
- 類的靜態成員變數編譯時被分配到靜態/全域性區,因此靜態成員變數是屬於類的,所有物件共用一份,不計入類的記憶體空間。
- 行內函數(宣告和定義都要加inline)也是存放在程式碼區,行內函數在被呼叫時,編譯器會用行內函數的程式碼替換掉函式,避免了函式跳轉和保護現場的開銷。不要將成員函式的這種儲存方式和inline(內聯)函式的概念混淆。不要誤以為用inline宣告(或預設為inline)的成員函式,其程式碼段佔用物件的儲存空間,而不用inline宣告的成員函式,其程式碼段不佔用物件的儲存空間。不論是否用inline宣告(或預設為inline),成員函式的程式碼段都不佔用物件的儲存空間。用inline宣告的作用是在呼叫該函式時,將函式的程式碼段複製插人到函式呼叫點,而若不用inline宣告,在呼叫該函式時,流程轉去函式程式碼段的入口地址,在執行完該函式程式碼段後,流程返回函式呼叫點。inline與成員函式是否佔用物件的儲存空間無關
綜上所述
- 同一個類建立的多個物件,其資料成員是各用各的,互不相通(靜態成員變數是共享的)。
- 成員函式是共享共用的,多個物件共用一份程式碼,所有類成員函式和非成員函式程式碼存放在程式碼區。
- 不論成員函式在類內定義還是在類外定義,成員函式的程式碼段都用同一種方式儲存。
參考連結:
C++成員函式在記憶體中的儲存方式
C++ 類在記憶體中的儲存方式
原文連結:https://blog.csdn.net/luolaihua2018/article/details/110736211