C++中對(duì)象模型分析

C++對(duì)象模型

首先來(lái)說(shuō)一說(shuō)class的本質(zhì):

  • class是一種特殊的struct
    • 在內(nèi)存中class依舊可以看作變量的集合
    • class與struct遵循相同的內(nèi)存對(duì)齊規(guī)則
    • class中的成員函數(shù)與成員變量是分開存放的
      • 每個(gè)對(duì)象有獨(dú)立的成員變量
      • 所有對(duì)象共享類中的成員函數(shù)

然后再來(lái)看一個(gè)問(wèn)題:

class A
{
    int i;
    int j;
    char c;
    double d;
};

struct B
{
    int i;
    int j;
    char c;
    double d;
};

有上述1個(gè)類A、一個(gè)結(jié)構(gòu)體B,請(qǐng)問(wèn)執(zhí)行下列語(yǔ)句輸出多少?

sizeof(A) = ?
sizeof(B) = ?

最終輸出結(jié)果為:

sizeof(A) = 24
sizeof(B) = 24

再舉一個(gè)例子:

#include <iostream>
#include <string>

using namespace std;

class A
{
    int i;
    int j;
    char c;
    double d;
public:
    void print()
    {
        cout << "i = " << i << ", "
             << "j = " << j << ", "
             << "c = " << c << ", "
             << "d = " << d << endl;
    }
};

struct B
{
    int i;
    int j;
    char c;
    double d;
};

int main()
{
    A a;
    
    cout << "sizeof(A) = " << sizeof(A) << endl;    // 20 bytes
    cout << "sizeof(a) = " << sizeof(a) << endl;
    cout << "sizeof(B) = " << sizeof(B) << endl;    // 20 bytes
    
    a.print();
    
    //用B指針類型指向類A的對(duì)象
    B* p = reinterpret_cast<B*>(&a);
    
    //通過(guò)指針修改A對(duì)象的各個(gè)值
    p->i = 1;
    p->j = 2;
    p->c = 'c';
    p->d = 3;
    
    a.print();
    
    p->i = 100;
    p->j = 200;
    p->c = 'C';
    p->d = 3.14;
    
    a.print();
    
    return 0;
}

最終輸出結(jié)果為:

sizeof(A) = 24
sizeof(a) = 24
sizeof(B) = 24
i = 0, j = 1, c =  , d = 2.26503e-314
i = 1, j = 2, c = c, d = 3
i = 100, j = 200, c = C, d = 3.14

從輸出結(jié)果結(jié)合代碼可以看出:
1、 類A 結(jié)構(gòu)體B 內(nèi)存大小是一樣的
2、在定義類A的對(duì)象后,未初始化時(shí),輸出成員變量都是未初始化的值
3、使用結(jié)構(gòu)體B的指針指向類A對(duì)象時(shí),可以直接修改成員變量的值

分析得出:

  • 運(yùn)行時(shí)的類對(duì)象退化為結(jié)構(gòu)體的形式
    • 所有成員變量在內(nèi)存中依次排布
    • 成員變量間可能存在內(nèi)存空隙
    • 可以通過(guò)內(nèi)存地址直接訪問(wèn)成員變量
    • 訪問(wèn)權(quán)限關(guān)鍵字在運(yùn)行時(shí)失效
  • 類中的成員函數(shù)位于代碼段中
  • 調(diào)用成員函數(shù)時(shí)對(duì)象地址作為參數(shù)隱式傳遞
  • 成員函數(shù)通過(guò)對(duì)象地址訪問(wèn)成員變量
  • C++語(yǔ)法規(guī)則隱藏了對(duì)象地址的傳遞過(guò)程

繼承對(duì)象模型

  • 在C++編譯器的內(nèi)部 類可以理解為結(jié)構(gòu)體
  • 子類是由父類成員疊加子類新成員得到的
class Derived : public Demo
{
    int mk;
};
Demo =>     int mi;     <=  Derived
Demo =>     int mj;     <=  Derived
            int mk;     <=  Derived

首先來(lái)看一個(gè)例子:

#include <iostream>
#include <string>

using namespace std;

//類Demo
class Demo
{
//子類可以直接訪問(wèn)
protected:
    int mi;
    int mj;
public:
    //虛函數(shù)print
    virtual void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << endl;
    }
};

//類Derived 繼承Demo
class Derived : public Demo
{
    //子類中的成員變量mk
    int mk;
public:
    //帶參構(gòu)造函數(shù)
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    //子類成員函數(shù)print
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << ", "
             << "mk = " << mk << endl;
    }
};

//結(jié)構(gòu)體Test
struct Test
{
    //包含1個(gè)指針 3個(gè)int類型變量
    //這個(gè)指針是指向虛函數(shù)表的指針
    //虛函數(shù)表指針位于對(duì)象最開始的位置
    void* p;
    int mi;
    int mj;
    int mk;
};

int main()
{
    //輸出Demo類和Derived類
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;         
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;  
    
    //定義并初始化d
    Derived d(1, 2, 3);
    //使用結(jié)構(gòu)體Test指針指向Derived類對(duì)象
    Test* p = reinterpret_cast<Test*>(&d);
    
    cout << "Before changing ..." << endl;
    
    //輸出成員變量以及父類的成員變量
    d.print();
    
    //直接通過(guò)p指針來(lái)賦值d的成員變量
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    
    cout << "After changing ..." << endl;
    
    //輸出各成員變量
    d.print();
    
    return 0;
}

輸出結(jié)果如下:

sizeof(Demo) = 16
sizeof(Derived) = 24
Before changing ...
mi = 1, mj = 2, mk = 3
After changing ...
mi = 10, mj = 20, mk = 30

在這里說(shuō)明一下,由于是在64位系統(tǒng)下運(yùn)行,所以指針占8個(gè)字節(jié)。Demo占16個(gè)字節(jié),是包含了2個(gè)4字節(jié)的int變量,加上1個(gè)8字節(jié)指向虛函數(shù)表的指針,共16個(gè)字節(jié)。Derived占24個(gè)字節(jié)是因?yàn)閮?nèi)存對(duì)齊,直接給int類型的mk變量分配8個(gè)字節(jié),所以共24個(gè)字節(jié)。使用Test結(jié)構(gòu)體指向Derived類對(duì)象時(shí),要注意指向虛函數(shù)表的指針是位于對(duì)象內(nèi)存最開始的位置,void * p就是這個(gè)指針,其余的按順序排列。

  • C++多態(tài)的實(shí)現(xiàn)原理
    • 當(dāng)類中聲明虛函數(shù)時(shí),編譯器會(huì)在類中生成一個(gè)虛函數(shù)表
    • 虛函數(shù)表是一個(gè)存儲(chǔ)成員函數(shù)地址的數(shù)據(jù)結(jié)構(gòu)
    • 虛函數(shù)表是由編譯器自動(dòng)生成與維護(hù)的
    • virtual成員函數(shù)會(huì)被編譯器放入虛函數(shù)表中
    • 存在虛函數(shù)時(shí),每個(gè)對(duì)象中都有一個(gè)指向虛函數(shù)表的指針

當(dāng)有Demo類如下:

class Demo
{
    int mi, mj;
public:
    virtual int add(int value)
    {
        return mi + mj + value;
    }   
};

此時(shí)這個(gè)對(duì)象就會(huì)有一個(gè)指針,指向編譯器維護(hù)的虛函數(shù)表。虛函數(shù)表中保存這成員函數(shù)的地址:

VTABLE  =>  void Demo :: add(int value)

當(dāng)有Derived類繼承Demo類時(shí),如下:

class Derived : public Demo
{
    int mk;
public:
    virtual int add(int value)
    {
        return mk + value;
    }
};

此時(shí)這個(gè)對(duì)象也會(huì)有一個(gè)指針,指向編譯器維護(hù)的虛函數(shù)表。虛函數(shù)表中保存這成員函數(shù)的地址:

VTABLE  =>  void Derived :: add (int value)

當(dāng)執(zhí)行這個(gè)函數(shù)時(shí):

void run(Demo * p, int v)
{
    p -> add(v);
}

執(zhí)行時(shí),編譯器會(huì)確認(rèn) add( ) 是否為虛函數(shù)?
1、Yes -> 編譯器在對(duì)象 VPTR 所指向的虛函數(shù)表中查找 add ( ) 的地址
2、No -> 編譯器直接可以確定被調(diào)成員函數(shù)的地址
所以,調(diào)用順序如下:

p -> VPTR -> VTABLE  void (*pAdd)(int value) -> 0xFF010203

調(diào)用效率對(duì)比: 虛函數(shù) < 普通成員函數(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一個(gè)博客,這個(gè)博客記錄了他讀這本書的筆記,總結(jié)得不錯(cuò)?!渡疃忍剿鰿++對(duì)象模型》筆記匯總 1. C++對(duì)象模型與內(nèi)...
    Mr希靈閱讀 5,909評(píng)論 0 13
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,686評(píng)論 1 51
  • 1. 結(jié)構(gòu)體和共同體的區(qū)別。 定義: 結(jié)構(gòu)體struct:把不同類型的數(shù)據(jù)組合成一個(gè)整體,自定義類型。共同體uni...
    breakfy閱讀 2,273評(píng)論 0 22
  • 1. C++基礎(chǔ)知識(shí)點(diǎn) 1.1 有符號(hào)類型和無(wú)符號(hào)類型 當(dāng)我們賦給無(wú)符號(hào)類型一個(gè)超出它表示范圍的值時(shí),結(jié)果是初始值...
    Mr希靈閱讀 18,171評(píng)論 3 82
  • 1.面向?qū)ο蟮某绦蛟O(shè)計(jì)思想是什么? 答:把數(shù)據(jù)結(jié)構(gòu)和對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作的方法封裝形成一個(gè)個(gè)的對(duì)象。 2.什么是類?...
    少帥yangjie閱讀 5,125評(píng)論 0 14

友情鏈接更多精彩內(nèi)容