Re: [問題] virtual function 的疑惑2
(呵欠) ㄟ...好像沒人要回這篇,我只好就我所知的隨便寫一下...
上一篇講了 class member function 的原理,現在我們來看看 virtual function。
(以下我講的 C++ 物件實作方式都只是一個常用的模形,各家 Compiler 真的用的
方法可能都有些微不同)
當你這樣寫的時候:
class Foo {
public:
void f();
virtual void g();
private:
int my_data;
};
Foo* obj = ...;
obj->f();
obj->g();
Compiler 是這樣看待它的...
struct VTable {
void (*g)(Foo* this); // for virtual member function Foo::g()
};
struct Foo {
VTable* vtable;
int my_data;
};
void Foo_f(Foo* this); // for non-virtual member function Foo::f()
Foo* obj = ...;
Foo_f(obj); // obj->f();
( *(obj->vtable->g) )(obj) // obj->g();
也就是說呢,class 中的 virtual member function 其實是個 function pointer,
呼叫它的時候,其實是去 vtable 中查出這個 function pinter 再呼叫。
我這邊為了說明簡單起見,先乎略掉上一篇所說的多重繼承 offset 問題,
真正的 vtable 其實還要多記錄一個 offset 來處理多重繼承的情況。
那 vtable 中的 function pointer 是指向哪裡呢?答案是:它是在呼叫 ctor 時,
compiler 偷偷幫你做好了初始化。
void Foo_g(Foo* this) // Foo::g() 的實作
{
// ...
}
VTable foo_vtable; // 這個 vtable 實體是給所有 Foo 物件用的
foo_vtable.g = Foo_g; // 它的 g 這個欄位,指向你的實作
void Foo_ctor(Foo* this) // Foo 的建構式
{
...
... // 這邊是你在 Foo 的建構式中寫的東西
...
this->vtable = &foo_vtable; // 這行是 compiler 幫你加的
}
所以當你呼叫 Foo 的 virtual function 時,compiler 會找出 obj 的 vtable,
從裡面那個對應的 function pointer 得到正確的位址來呼叫。
那現在我們要繼承 Foo 並且改寫 g(),會發生什麼事呢?
class Bar : public Foo {
public:
virtual void g();
private:
double my_child_data;
};
同樣地,compiler 為了達成 dynamic binding,幫你產生了對應的 code:
struct Bar {
Foo_vtable* vtable;
int my_data; // 來自 Foo::my_data
double my_child_data; // Bar 自己的 member variable
};
void Bar_g(Bar* this); // Bar::g() 的實作
VTable bar_vtable; // Bar 的 vtable
bar_vtable.g = Bar_g; // 其中 g 的欄位指向 Bar::g() 的實作
void Bar_ctor(Bar* this) // Bar 的建構式
{
Foo_ctor(this); // 它會先呼叫爸爸的建構式
...
... // 這邊是你寫在 Bar::Bar() 中的東西
...
this->vtable = bar_vtable; // 這行是 compiler 幫你加的
}
這麼一來,我們就可以達到 virtual invocation 的效果:
Foo* a;
a = new Foo;
a->g(); // 呼叫的是 Foo::g(),因為在建構式中,
// compiler 幫你把 vtable 指向 foo_vtable
delete a;
a = new Bar;
a->g(); // 同樣的道理,這時候 vtable 指向 bar_vtable
// 所以會呼叫到 Bar::g()
現在我們回到你的問題...
※ 引述《QQ29 (我愛阿蓉)》之銘言:
: 1.
: class Base1 {
: public:
: void f() {cout<<"1"; }
: };
: int main()
: {
: int *p=(new int);
: Base1 *a1 = (Base1*)p;
: a1->f();
: return 0;
: }
: 這個情況 a1->呼叫的到f() 印出1.... 這是因為什麼原因他可以link到f()?
因為 f 並不是 virtual function,它只是個一般的 member function
member function 和普通的 function 只有一個差異:compiler 會幫你傳 this
所以你可以想成 f 只是如下的普通 function:
void Base1_f(Base1* this)
{
cout <<"1";
}
: 想一下 印出sizeof(Base1) 會 印1...這個1不知道是什麼的size...
因為 C++ 的 class 至少占用 1byte...即使它沒有任何成員
我忘了這是不是標準,好像是吧 (真隨便 XD)
: 當我加上virtual 就會run time 壞掉
當你加上 virtual,意味著在呼叫它的時候,compiler 會從 a1 這個指標中,
去尋找裡面的 vtable 並試圖取出 Base::f() 的位址,
然而 a1 事實上是個指向 int 的 pointer,裡面並沒有 vtable,
硬來的結果當然是 runtime error
: 2.
: http://nopaste.csie.org/93cdd
: 這是簡單的dynamic binding
: 如果以上一篇的觀念(我想可能還不夠解釋dynamic binding)
: a1->他自己的vptr 只有記載她自己的a1::f()
: 他是什麼機制可以知道Base3也有繼承他 也有override f()?
: 我認為他是 a1->Base1::f() => Base2::f() => Base3::f()
: 但中間的=>關係 是哪裡有記錄呢?
如前所述...並非 base 知道某個 child 繼承自己
而是所有的物件都有一個 vtable 的指標
它們用這個指標來分辦自己是屬於哪一個 class
: 3.
: 我印出sizeof(Base3)印出4
: 覺得很怪 他繼承 Base2至少要有Base2的vptr並且還得到Base1的vptr 和自己的vptr
: 想說印出12比較合理
不對,如果你是一個單一的繼承鏈,沒有多重繼承,
那 vtable 的指標只有一個
: 我觀念錯很大
: 於是寫成 多繼承一個
: class Base3 : public Base2, public Base1{
: 印出8...
: 故意繼承個空class 也印出8
: class Base4{};
: class Base3 : public Base4{
: 仔細想一下 如果我繼承觀念沒錯的話
: class A{int x}; class B:public A{double y;};
: B會擁有 父親的A::x(4) 和自己繼承來的B::x(4)
: 和原本自己的B::y(8)
呃...你的繼承觀念顯然有錯
繼承後並不會多一份 B::x 出來
而且我跑的結果:
class A { int x; };
class B : public A { double y; };
cout << sizeof(B);
結果非並 24 而是 16 (使用 vc9 測試的結果)
是 16 的原因在於 compiler 會試圖讓 B::y 的位址 align 在 8byte 上
所以會調整 class member 的 layout
: sizeof(B) = 24的原因應該是 有三個變數 所以是3* max大小 所以=3*8=24..
你如果得到 sizeof(B) = 24
應該是因為 B 裡面有 virtual function
為了存 vtable 並且讓 B::y align 在 8byte 上面
compiler 因而在 class 中塞了許多沒用到的空間
完全不是因為有三個變數然後 8*3=24...
: 但如果是扯到vptr的話
: 就我測試的結論
: 感覺vptr還是只有一個 而 vtable就一直增加記錄有誰override他 就在加上去
: 所以可能可以解釋 2.的問題 由一個vptr可以一直link到 Base3::f()
: 但這樣我就不能解釋
: class Base4{};
: class Base3 : public Base4{ 得到8
class Base{};
class Derived : public Base {};
cout << sizeof(Derived);
我的結果是 1 喔 (gcc 和 vc9 的結果相同)
如果 Base 或 Derived 有 virtual function
結果則都是 4
: 和
: class Base3 : public Base2, public Base1{ 得到8
class Base1{};
class Base2{};
class Derived : public Base1, public Base2 {};
cout << sizeof(Derived);
結果為 1 (gcc 和 vc9 皆是)
若 Base1 和 Base2 其中一個有 virtual function
那結果為 4 (vptr 占的空間)
若 Base1 和 Base2 兩邊都有 virtual function
則結果為 8 (兩份 vptr 占的空間)
: 有諸多觀念無法融會貫通
: 手邊的書籍也沒有寫的這麼深入= = (該買深入一點的書了)
: 雖然我覺得不懂也沒差反正可以run就好 但是好奇心還是讓我想問這幾項問題
: 問題繁多@@請教各位
: 非常感謝
我覺得你很有心要理解 C++,這很好。
不過的確是該買書的時候了,這些東西書上講的都比我寫的詳細許多。
然後順便找一本 Computer Organization & Assembly 的書來看,
這樣你才會知道 CPU 究竟如何執行你的 code
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.29.108
推
02/11 16:58, , 1F
02/11 16:58, 1F
推
02/11 17:00, , 2F
02/11 17:00, 2F
推
02/11 17:08, , 3F
02/11 17:08, 3F
推
02/11 17:46, , 4F
02/11 17:46, 4F
感謝指正
※ 編輯: littleshan 來自: 140.112.29.108 (02/11 17:53)
推
02/11 18:42, , 5F
02/11 18:42, 5F
→
02/11 23:51, , 6F
02/11 23:51, 6F
推
02/21 02:54, , 7F
02/21 02:54, 7F
討論串 (同標題文章)