本帖最后由 loca1h0st 于 2025-7-5 01:29 编辑
[C++] 纯文本查看 复制代码 class Base1 {
public:
virtual void func1() { std::cout << "Base1::func1" << std::endl; }
virtual void func2() { std::cout << "Base1::func2" << std::endl; }
private:
int _b1;
};
class Base2 {
public:
virtual void func1() { std::cout << "Base2::func1" << std::endl; }
virtual void func2() { std::cout << "Base2::func2" << std::endl; }
private:
int _b2;
};
class Derive: public Base1, public Base2 {
public:
virtual void func1() { std::cout << "Derive::func1" << std::endl; }
virtual void func2() { std::cout << "Derive::func2" << std::endl; }
virtual void func3() { std::cout << "Derive::func3" << std::endl; }
private:
int _b1;
};
上面我们定义了两个基类和一个派生类,派生类继承自Base1,Base2 。并在派生类中重写了func1与func2, 这使得在 Derive 对象模型中,func1 和 func2 会分别覆盖掉 Base1 和 Base2 中对应的虚函数表项,即:- Base1 的虚表中原本指向 Base1::func1 和 Base1::func2 的函数指针会被替换为 Derive::func1 和 Derive::func2。
- 同样,Base2 的虚表中原本指向 Base2::func1 和 Base2::func2 的函数指针也会被替换为 Derive::func1 和 Derive::func2。
- 由于 Derive 还增加了 func3(),因此该函数会被加入到 Derive 中自己的虚函数表(通常在 Base1 的继承路径那边虚表中扩展)。
1
下面我们来写段代码来验证一下,到时是不是在Base1的虚表中(vft)[C++] 纯文本查看 复制代码 typedef void(*VFPTR)();
void printVtable(VFPTR* vTable)
{
for (int i = 0; vTable[i] != nullptr; i++)
{
printf("[%-2d] ==> 0x%p ==> ", i, vTable[i]);
VFPTR func = vTable[i];
func();
}
cout << endl;
}
int main()
{
Derive d;
cout << sizeof(d) << endl;
VFPTR* vTable1 = (VFPTR*)(*(int*)&d);
printVtable(vTable1);
Base2 b2 = d;
VFPTR* vtable2 = (VFPTR*)(*(int*)&b2);
printVtable(vtable2);
return 0;
}
2
我们可以看到在vs2019编译器下,func3 并没有在调试窗口中显示。但是可以通过程序得知,func3 就是保存在Base1的vft中。但是我们仔细观察发现在派生类中重写的func1 在Base1 与 Base2 vft中地址并不一致,这是为什么呢?
3
下面通过查看汇编一探究竟
4
F11 步进 vs编译器 call 下 一般是jmp指令
5
直接跳转到了func1 的地址。那么再来看看ptr2是怎么调用func1的
6
连续两次F11 步进
7
可以看到ecx - 8。有两个问题ecx 是啥? 为什么减 8 ?
8
|