文章目录
本代码实验在Windows下vs2019(x86)完成
1.虚函数表指针位置分析
1.1、虚函数表指针的定义
一个类中如果有虚函数,生成这个类对象的时候,该对象里就有一个指针(vptr)
用来指向这个虚函数表的起始地址。虚函数表指针(vptr)
是C++中用于指向虚函数表的指针。虚函数表是一个存储类中虚函数地址的表,它使得程序能够在运行时通过虚函数表指针找到并调用相应的虚函数。
1.2、虚函数表指针的位置
通过编写一下代码测试
#include <iostream>
class A
{
public:
int i = 0; //一个成员变量
virtual void testfun()
{}
// 虚函数
};
int main()
{
A a;
int alen = sizeof(a); // 对象a总大小
int ilen = sizeof(int); // i 占4字节
int vptrlen = alen - ilen; // 虚函数表指针占四字节
char* p1 = reinterpret_cast<char*>(&a); // 类型转换, a是对象的首地址
char* p2 = reinterpret_cast<char*>(&(a.i));
std::cout << "对象a的总大小: " << alen << std::endl;
std::cout << "int i 的大小: " << ilen << std::endl;
std::cout << "vptr 的大小: " << vptrlen << std::endl;
if (p1 == p2)
{
// 说明a.i和a的位置相同,则成员变量i在a对象内存的上面位置,那么虚函数表指针在下面位置
std::cout << "虚函数表指针位于对象内存的末尾" << std::endl;
}
else
{
std::cout << "虚函数表指针位于对象内存开头" << std::endl; //本条件会成立
}
return 0;
}
结论:虚函数表指针占4字节(x86),位于对象内存的开头
对象a的模型
2. 父类虚函数的手动调用
2.1 通过虚函数表查看虚函数地址
代码如下
#include <iostream>
class Base
{
public:
virtual void f()
{
std::cout << "Base::f()被调用" << std::endl;
}
virtual void g()
{
std::cout << "Base::g()被调用" << std::endl;
}
virtual void h()
{
std::cout << "Base::h()被调用" << std::endl;
}
};
int main()
{
Base* d = new Base(); //派生类指针
long* pvptr = (long*)d; //将对象d的指针转换成long* 型, 目前d对象里只有虚函数表指针
long* vptr = (long*)(*pvptr); // 获得虚函数表的地址
//根据虚函数打印Derive类的虚函数的地址
for (int i = 0; i < 3; ++i)
{
printf("vptr[ %d ] = 0x: %p\n", i, vptr[i]);
}
return 0;
}
2.2 通过虚函数表手动调用虚函数
代码如下
#include <iostream>
class Base
{
public:
virtual void f()
{
std::cout << "Base::f()被调用" << std::endl;
}
virtual void g()
{
std::cout << "Base::g()被调用" << std::endl;
}
virtual void h()
{
std::cout << "Base::h()被调用" << std::endl;
}
};
int main()
{
Base* d = new Base(); //派生类指针
long* pvptr = (long*)d; //将对象d的指针转换成long* 型, 目前d对象里只有虚函数表指针
long* vptr = (long*)(*pvptr); // 获得虚函数表的地址
//根据虚函数打印Derive类的虚函数的地址
for (int i = 0; i < 3; ++i)
{
printf("vptr[ %d ] = 0x: %p\n", i, vptr[i]);
}
using func = void(*)(void); //定义一个返回值形参为空的函数指针类型,方便实现函数的调用
func f = (func)vptr[0]; //指向父类第一个虚函数 virtual void f()
func g = (func)vptr[1]; //指向子类第二个的虚函数virtual void g()
func h = (func)vptr[2]; //指向父类第三个虚函数 virtual void h()
//进行调用
f();
g();
h();
return 0;
}
3. 继承关系下子类的虚函数手动调用
3.1通过虚函数表查看虚函数的地址
代码如下
#include <iostream>
class Base
{
public:
virtual void f()
{
std::cout << "Base::f()被调用" << std::endl;
}
virtual void g()
{
std::cout << "Base::g()被调用" << std::endl;
}
virtual void h()
{
std::cout << "Base::h()被调用" << std::endl;
}
};
class Derive : public Base
{
public:
void g()
{
std::cout << "Derive::g()被调用" << std::endl;
}
};
int main()
{
Derive* d = new Derive(); //派生类指针
long* pvptr = (long*)d; //将对象d的指针转换成long* 型, 目前d对象里只有虚函数表指针
long* vptr = (long*)(*pvptr); // 获得虚函数表的地址
//根据虚函数打印Derive类的虚函数的地址
for (int i = 0; i < 4; ++i)
{
printf("vptr[ %d ] = 0x: %p\n", i, vptr[i]);
}
return 0;
}
总结:
- 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个
nullptr
。
3.2 通过虚函数表手动调用虚函数
代码如下
#include <iostream>
class Base
{
public:
virtual void f()
{
std::cout << "Base::f()被调用" << std::endl;
}
virtual void g()
{
std::cout << "Base::g()被调用" << std::endl;
}
virtual void h()
{
std::cout << "Base::h()被调用" << std::endl;
}
};
class Derive : public Base
{
public:
void g()
{
std::cout << "Derive::g()被调用" << std::endl;
}
virtual void add()
{
std::cout << "Derive::add()被调用" << std::endl;
}
};
int main()
{
Derive* d = new Derive(); //派生类指针
long* pvptr = (long*)d; //将对象d的指针转换成long* 型, 目前d对象里只有虚函数表指针
long* vptr = (long*)(*pvptr); // 获得虚函数表的地址
using func = void(*)(void); //定义一个返回值形参为空的函数指针类型,方便实现函数的调用
func f = (func)vptr[0]; //指向父类第一个虚函数 virtual void f()
func g = (func)vptr[1]; //指向子类重写的虚函数 void g()
func h = (func)vptr[2]; //指向父类第三个虚函数 virtual void h()
func add = (func)vptr[3]; // 子类新增的虚函数 virtual void add()
//进行调用
f();
g();
h();
add();
return 0;
}
总结:
- 派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生
类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己
新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
4 虚函数表分析
代码如下
class Base
{
public:
virtual void f()
{
std::cout << "Base::f()被调用" << std::endl;
}
virtual void g()
{
std::cout << "Base::g()被调用" << std::endl;
}
virtual void h()
{
std::cout << "Base::h()被调用" << std::endl;
}
};
class Derive : public Base
{
public:
void g()
{
std::cout << "Derive::g()被调用" << std::endl;
}
virtual void add()
{
std::cout << "Derive::add()被调用" << std::endl;
}
};
如图