類所占內存的大小是由成員變量(靜態變量除外)決定的,虛函數指針和虛基類指針也屬於數據部分,成員函數是不計算在內的。
因為在編譯器處理后,成員變量和成員函數是分離的。成員函數還是以一般的函數一樣的存在。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