C++對(duì)象模型(2)

本文預(yù)覽:

  • 關(guān)于vptr(虛函數(shù)表指針)和vtbl(虛函數(shù)表)
  • 關(guān)于this指針
  • 關(guān)于Dynamic Binding(動(dòng)態(tài)綁定)
  • new delete操作符重載

關(guān)于vptr(虛函數(shù)表指針)和vtbl(虛函數(shù)表)

虛函數(shù)表指針和虛函數(shù)表是C++實(shí)現(xiàn)多態(tài)的核心機(jī)制,理解vtbl和vptr的原理是理解C++對(duì)象模型的重要前提。
class里面method分為兩類:virtual 和non-virtual。非虛函數(shù)在編譯器編譯是靜態(tài)綁定的,所謂靜態(tài)綁定,就是編譯器直接生成JMP匯編代碼,對(duì)象在調(diào)用的時(shí)候直接跳轉(zhuǎn)到JMP匯編代碼執(zhí)行,既然是匯編代碼,那么就是不能在運(yùn)行時(shí)更改的了;虛函數(shù)的實(shí)現(xiàn)是通過虛函數(shù)表,虛函數(shù)表是一塊連續(xù)的內(nèi)存,每個(gè)內(nèi)存單元中記錄一個(gè)JMP指令的地址,通過虛函數(shù)表在調(diào)用的時(shí)候才最終確定調(diào)用的是哪一個(gè)函數(shù),這個(gè)就是動(dòng)態(tài)綁定。

關(guān)于vptr和vtbl

class的內(nèi)部有一個(gè)virtual函數(shù),其對(duì)象的首個(gè)地址就是vptr,指向虛函數(shù)表,虛函數(shù)表是連續(xù)的內(nèi)存空間,也就是說,可以通過類似數(shù)組的計(jì)算,就可以取到多個(gè)虛函數(shù)的地址,還有一點(diǎn),虛函數(shù)的順序和其聲明的順序是一直的。

通過虛函數(shù)表來調(diào)用虛函數(shù),繞過private的限制:

typedef void(*Fun)(void);

class Base {
private:
    virtual void f() {cout<<"Base::f()"<<endl;}
    virtual void g() {cout<<"Base::g()"<<endl;}
    virtual void h() {cout<<"Base::h()"<<endl;}

}

int main()
{
  Base b;
  Fun fp = nullprt;
  for(int i = 0; i < 3; i++)
  {
      fp = (Fun)*((long*)*(long*)(&b) + i);
      fp();
  }
}
運(yùn)行結(jié)果:
Base::f()
Base::g()
Base::h()

需要注意的是,由于我的電腦是64位的系統(tǒng),vptr轉(zhuǎn)成long,八個(gè)字節(jié),32位的int就可以了,這個(gè)根據(jù)自己的環(huán)境去修改就可以了。&b取vptr,轉(zhuǎn)成long*,取出來是vtbl ,同樣需要轉(zhuǎn)成八字節(jié),不然在指針偏移的時(shí)候肯定就錯(cuò)了,也就是+i,虛函數(shù)地址取出來要轉(zhuǎn)換成可執(zhí)行的函數(shù)指針,這樣即使在class聲明的時(shí)候做了private限制,在指針面前直接就繞過去了。

關(guān)于this指針

上一張很久之前的圖了:

關(guān)于this指針

每一個(gè)成員函數(shù)都有一個(gè)默認(rèn)參數(shù),那就是this,this代表對(duì)象本身,但是this究竟是什么呢?this就是對(duì)象的地址。

關(guān)于Dynamic Binding(動(dòng)態(tài)綁定)

怎么理解動(dòng)態(tài)綁定和靜態(tài)綁定,一般來說,對(duì)于類成員函數(shù)(不論是靜態(tài)還是非靜態(tài)的成員函數(shù))都不需要?jiǎng)?chuàng)建一個(gè)在運(yùn)行時(shí)的函數(shù)表來保存,他們直接被編譯器編譯成匯編代碼,這就是所謂的靜態(tài)綁定;所謂動(dòng)態(tài)綁定就是對(duì)象在被創(chuàng)建的時(shí)候,在它運(yùn)行的時(shí)候,其所攜帶的虛函數(shù)表,決定了需要調(diào)用的函數(shù),也就是說,程序在編譯完之后是不知道的,要在運(yùn)行時(shí)才能決定到底是調(diào)用哪一個(gè)函數(shù)。這就是所謂的靜態(tài)綁定和動(dòng)態(tài)綁定。
參考: C++this指針-百度百科

動(dòng)態(tài)綁定需要三個(gè)條件同時(shí)成立:

1 指針調(diào)用
2 up-cast (有向上轉(zhuǎn)型,父類指針指向子類對(duì)象)
3 調(diào)用的是虛函數(shù)

通過兩張圖看看匯編代碼:

靜態(tài)綁定

a.vfunc1()調(diào)用虛函數(shù),那么a調(diào)用的是A的虛函數(shù),還是B的虛函數(shù)?對(duì)象調(diào)用不會(huì)發(fā)生動(dòng)態(tài)綁定,只有指針調(diào)用才會(huì)發(fā)生動(dòng)態(tài)綁定。120行下面發(fā)生的call是匯編指令,call后面是一個(gè)地址,也就是函數(shù)編譯完成之后的地址了。

再看第二張:

動(dòng)態(tài)綁定

up-cast、指針調(diào)用、虛函數(shù)三個(gè)條件都滿足動(dòng)態(tài)調(diào)用,call指令后面不再是靜態(tài)綁定簡(jiǎn)單的地址,翻譯成C語言大概就是(*(p->vptr)[n](p)),通過虛函數(shù)表來調(diào)用函數(shù)。

new delete操作符重載

舉個(gè)例子:

String* s = new String("hello world");

new 操作符主要干了兩件事

  • 調(diào)用operator new分配內(nèi)存空間
  • 調(diào)用構(gòu)造函數(shù)

這個(gè)在之前的文章中C++如何設(shè)計(jì)一個(gè)類2(含指針的類)將過,這里寫的就簡(jiǎn)單一些了。

這里我們要重載operator new,需要注意的是我們可以重載全局和成員操作符,兩個(gè)影響范圍是不一樣的。

void* myMalloc(size_t size)
{
    void* p = malloc(size);
    std::cout<<"myMalloc()"<<std::endl;
    return p;
}

void myFree(void* ptr)
{
    free(ptr);
    std::cout<<"myFree()"<<std::endl;
}

//會(huì)覆蓋掉前面,影響范圍是全局的
void* operator new(size_t size){return myMalloc(size);}
void operator delete(void* ptr) { myFree(ptr);}

class Apple{
public:
    Apple(){std::cout<<"Apple::Apple()"<<std::endl;}
    void* operator new(size_t size){std::cout<<"+++++++"<<std::endl; return myMalloc(size);}
    void operator delete(void* ptr) {std::cout<<"-------"<<std::endl;return myFree(ptr);}
};
int main(int argc, const char * argv[]) {
    
    //調(diào)用class重載的
    Apple* apple = new Apple;
    
    delete apple;
    
    //強(qiáng)制調(diào)用全局的
    Apple* app = ::new Apple;
    
    ::delete app;

    return 0;
}

new[] 和delete[]是一個(gè)道理。

最后編輯于
?著作權(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)容

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