C++ 支持多重繼承,但將子類指針轉為 void* 再轉回父類指針時,若父類非第一繼承順序,會發生地址偏移,導致調用錯誤的虛函數。
class IA { public: virtual ~IA(){} virtual void a() = 0; };
class IB { public: virtual ~IB(){} virtual void b() = 0; };
class CMulti : public IA, public IB {
public:
void a() { cout << "C::a()" << endl; }
void b() { cout << "C::b()" << endl; }
};
void testCastA(void* p) {
IA* a = (IA*)p; // 危險:直接從 void* 強轉
a->a();
}
void testCastB(void* p) {
IB* b = (IB*)p;
b->b();
}
int main() {
CMulti* c = new CMulti;
cout << "CMulti* = " << c << endl;
// 1. 直接強轉 void*
testCastA((void*)c);
testCastB((void*)c);
// 2. 先 static_cast 再轉 void*
testCastA((void*)static_cast(c));
testCastB((void*)static_cast(c));
// 3. 先 dynamic_cast 再轉 void*
testCastA((void*)dynamic_cast(c));
testCastB((void*)dynamic_cast(c));
return 0;
}
CMulti* = 0x00AFF760 cast to void*, then to IA or IB: cast from void*(00AFF760) to IA*: 00AFF760 → C::a() cast from void*(00AFF760) to IB*: 00AFF760 → C::a() 【錯誤!應調用 b()】 static_cast to void*, then to IA or IB: cast from void*(00AFF760) to IA*: 00AFF760 → C::a() cast from void*(00AFF764) to IB*: 00AFF764 → C::b() 【正確】 dynamic_cast to void*, then to IA or IB: cast from void*(00AFF760) to IA*: 00AFF760 → C::a() cast from void*(00AFF764) to IB*: 00AFF764 → C::b() 【正確】

CMulti 繼承 IA(第一父類)和 IB(第二父類)IA 子對象從偏移 0 開始,IB 子對象從偏移 4 開始CMulti* 地址 = IA* 地址 = 0x00AFF760IB* 地址 = CMulti* + 4 = 0x00AFF764| 方式 | 到 IA* | 到 IB* | 原理 |
|---|---|---|---|
(void*)c 直接強轉 | 正確 (0) | 錯誤 (0,應為 +4) | void* 擦除類型信息,後續強轉視為單繼承,無偏移 |
static_cast | 正確 (0) | 正確 (+4) | 編譯期知曉繼承關係,自動計算偏移 |
dynamic_cast | 正確 (0) | 正確 (+4) | 運行期通過 RTTI 查詢繼承關係,計算偏移 |
std::any (C++17)、std::variant 或基類指針傳遞static_cast 到目標父類指針,再轉 void*pair,恢復時根據 type_id 正確轉換多重繼承時,子類指針轉換為非第一繼承父類指針會發生地址偏移。void* 擦除類型信息導致偏移信息丟失,直接強轉回非第一父類會產生錯誤地址。
static_cast/dynamic_cast 保留類型信息,能正確調整指針vbptr,偏移計算更複雜此問題是 C++ 多重繼承與類型擦除交互的經典陷阱。現代 C++ 開發應盡量避免 void* 使用,若不可避免,務必在轉 void* 前通過 static_cast 完成指針調整。理解對象內存佈局(vptr、vbptr、偏移量)是診斷此類問題的關鍵。