1. 程式人生 > 其它 >C++中類所佔的記憶體大小以及成員函式的儲存位置

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

我們嚮往遠方,卻忽略了此刻的美麗