天天看點

c++ vtable 虛函數表

1.作用:

用于有虛函數對象的指針,其在運作期間決定實際應該執行的函數的位址

2.記憶體布局:

記憶體的開頭位置(64位),即記憶體開頭8位元組内容為vtable的的位址值

而vtable順序存放函數位址值(64位順序數組)

3.代碼調用:

#include <stdio.h>

#include <iostream>

class P { 

 public:
    virtual void test() {std::cout << "test " << std::endl;}
    virtual void testa() {std::cout << "testa " << std::endl;}

 private:
    int a;
    
};

class S : public P { 
 public:
    void test() {std::cout << "test s" << std::endl;}
    void testc() {std::cout << "test s c" << std::endl;} 

 private:
    int c;
};
int main() {
    P *p = new S();
    void *ptr = (void*)(*((long*)p));
    printf("%x\n", ptr);

    void (*fun)(void) = (void(*)(void))(*((long*)ptr));
    printf("%x\n", fun);
    (fun)();
    delete p;

    return 0;
}
           

void *ptr = (void*)(*((long*)p)); 擷取到vtable的位址

void (*fun)(void) = (void(*)(void))(*((long*)ptr));擷取函數指針

注:

1.隻有擁有virtual的類的對象才會有vtable,對于非virtual的函數,在編譯器就能直接指定調用,而無需額外操作來得到

case:

析構函數為什麼一般需要virtual;

假設P是S的父類,那麼預設情況下S的析構函數生成的代碼會自動包含調用父類析構函數的代碼,

這也就能說明當使用S s;等本地變量的時候,其是能夠處理完自己的析構函數再自動調用父類的析構函數(構造函數則相反),即代碼已經在子類的析構函數中,

但是當用P *p = new S();的時候,如果p的析構函數不是virtual的,那麼會隻有析構~P函數,這是因為,~P()并不是虛函數,那麼不會在vtable中,其決定析構函數的調用,

在編譯期間便根據P的類型,直接隻調用~P的

那麼當~P為虛函數的時候,~P是作為vtable的函數的,當其指向S()的時候,其~P()的函數實際是指向~S()的,是以便能完成析構函數的正确調用。

繼續閱讀