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

ATL 內部機制深度解析 (一):虛函數表與多重繼承記憶體佈局

     編輯:關於VC++

系列前言

本系列深入探討 ATL (Active Template Library) 內部實作技術。首篇聚焦 C++ 物件模型核心:虛函數表 (vtable)、虛指標 (vptr)、單一/多重繼承下的記憶體佈局,以及 ATL 如何利用 `offsetofclass` 宏操作介面指標。

基礎:空類別與資料成員


class Empty {};                    // sizeof = 1 (C++ 標準:非零大小)
class Point { int x, y; };         // sizeof = 8 (兩個 int)
template class Pt { T x, y; }; // Pt sizeof = 8

單一繼承:衍生類別大小 = 基底成員 + 衍生成員


class Point3D : public Point { int z; }; // sizeof = 12 (x,y,z)

記憶體佈局:基底成員在前,衍生成員在後,連續排列。

虛函數引入的變化


class Base {
    virtual void f() {}  // 引入虛函數
};
// sizeof(Base) = 4 (32位) / 8 (64位) — 增加一個 vptr

關鍵發現:vptr 位於物件最前端 (Visual C++ 實作)。

實驗驗證 vptr 位置


class Point {
    int x, y;
    virtual ~Point() {}
};

Point pt(5, 10);
int* p = reinterpret_cast(&pt);
p[0] = 100; // 企圖修改 x
p[1] = 200; // 企圖修改 y
// 實際結果:x=200, y=10 → p[0] 是 vptr,p[1] 才是 x
// 正確存取:p[1]=100 (x), p[2]=200 (y)

虛函數表結構

  • vptr 指向唯讀記憶體區域的虛函數表
  • 虛函數表 = 函式指標陣列,以 NULL 結尾
  • 每個含虛函數的類別有一份靜態虛表

// 手動呼叫虛函數 (教學用,生產碼切勿如此)
typedef void(*Fun)();
Base obj;
Fun pFun = reinterpret_cast(*reinterpret_cast(&obj))[0];
pFun(); // 呼叫第一個虛函數

多重繼承記憶體模型


class Base1 { virtual void f() {} };
class Base2 { virtual void f() {} };
class Base3 { virtual void f() {} };
class Derived : public Base1, public Base2, public Base3 {
    virtual void fd() {}
};
// sizeof(Derived) = 12 (3 個 vptr,各 4 bytes)

記憶體佈局:

Derived 物件:
+------------------+  <-- this (Base1*)
| Base1::vptr      |  指向 Base1 虛表 (含 f, fd)
+------------------+  <-- this+4 (Base2*)
| Base2::vptr      |  指向 Base2 虛表 (含 f)
+------------------+  <-- this+8 (Base3*)
| Base3::vptr      |  指向 Base3 虛表 (含 f)
+------------------+

衍生類別新增虛函數 (`fd`) 會加入第一個基底的虛表中 (Primary Base)。

ATL 關鍵技術:offsetofclass 宏

目的:編譯期計算衍生類別中指定基底子物件的位移。


#define _ATL_PACKING 8
#define offsetofclass(base, derived) \
    ((DWORD)(static_cast((derived*)_ATL_PACKING)) - _ATL_PACKING)

// 使用範例
class Base1 { virtual void f() {} };
class Base2 { virtual void f() {} };
class Derived : public Base1, public Base2 {};

offsetofclass(Base1, Derived) // = 0
offsetofclass(Base2, Derived) // = 4 (32位)

原理:將非零常數 (`_ATL_PACKING`) 轉為 `derived*`,`static_cast` 到 `base*` 會依 C++ 標準調整指標位移,相減即得位移量。

Don Box 替代實作


#define BASE_OFFSET(ClassName, BaseName) \
    (DWORD)(static_cast(reinterpret_cast(0x10000000)) - 0x10000000)

效果相同,使用較大常數避免對齊干擾。

實戰應用:透過位移呼叫基底虛函數


Derived d;
void* pBase1 = static_cast(&d) + offsetofclass(Base1, Derived);
static_cast(pBase1)->f(); // 呼叫 Base1::f()

void* pBase2 = static_cast(&d) + offsetofclass(Base2, Derived);
static_cast(pBase2)->f(); // 呼叫 Base2::f()

這正是 ATL `CComPtr`、`_ATL_INTMAP` 等機制查詢介面的核心邏輯。

下一篇預告

將探討 ATL 如何實作 `QueryInterface`、物件對應表 (`_ATL_OBJMAP`)、模組類別 (`CComModule`) 等核心架構。

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