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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM