首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C++ 虛函數表原理深度解析:從單繼承到多重繼承的 vtable 機制

     編輯:關於C語言

虛函數表(vtable)是 C++ 實現運行時多態的核心機制。本文通過代碼實測、內存圖解,系統講解 vtable 結構、繼承與覆蓋規則、多重繼承佈局、安全性風險,基於 32 位 Windows XP + VS2003 / Linux GCC 4.1.3 環境。

1. vtable 基礎:對象首地址存放 vptr


class Base { virtual void f(); virtual void g(); virtual void h(); };
Base b;
typedef void(*Fun)();
Fun pFun = (Fun)*((int*)*(int*)(&b)); // 取 vptr → vtable[0] → f()
pFun(); // 調用 Base::f

內存圖:

對象地址 → vptr → vtable → [f(), g(), h(), NULL/1]

vtable 末尾標誌:VC++ 用 NULL,GCC 用 1(有下一個 vtable)/0(最後)。

2. 一般繼承:無覆蓋 vs 有覆蓋

  • 無覆蓋:子類 vtable 繼承父類所有虛函數,順序不變,父類函數在前
  • 有覆蓋:覆蓋函數替換父類在 vtable 中的位置,新增虛函數追加在末尾

多態本質:Base* b = new Derive(); b->f() 通過 vptr 找到 Derive::f 地址並調用。

3. 多重繼承 vtable 佈局


class Base1 { virtual void f(); g(); h(); };
class Base2 { virtual void f(); g(); h(); };
class Base3 { virtual void f(); g(); h(); };
class Derive : public Base1, public Base2, public Base3 { virtual void f(); g1(); };

無覆蓋:每個基類獨立 vtable,子類函數放入第一個基類(Base1) vtable 末尾。

有覆蓋:所有基類 vtable 中的同名虛函數均被替換為派生類版本。確保任意基類指針指向派生對象時調用正確覆蓋函數。

4. 安全性風險:vtable 可被惡意利用

① 通過父類指針訪問子類私有虛函數

編譯期阻止 b1->g1(),但運行期可通過 vtable 索引調用:


pFun = (Fun)pVtab[0][3]; // Derive::g1 位於 Base1 vtable 第 4 位
pFun(); // 成功調用

② 繞過訪問控制調用 private/protected 虛函數


class Base { private: virtual void f() { cout << "Base::f"; } };
class Derive : public Base {};
Derive d;
((Fun)*((int*)*(int*)(&d)))(); // 直接調用 private Base::f

vtable 存儲函數地址,不保存訪問權限,運行期無權限檢查。

5. VC++ IDE 查看 vtable

Debug 模式下展開對象實例,可查看 __vfptr 指向的 vtable 內容(不完整)。

總結

vtable 是 C++ 多態的「地圖」:單繼承單表、多重繼承多表、覆蓋替換同位、訪問控制僅編譯期有效。理解其佈局有助於調試懸空指針、分析二進制兼容性、設計插件系統。記住:所有示例基於 32 位環境,64 位指針寬度翻倍,佈局邏輯不變。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved