虚函数表指针位置分析
类有虚函数,这个类会产生一个虚函数表
类对象有一个指针,指针(vptr)会指向这个虚函数表的开始地址
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <iostream>
class A{
public:
int m_val;
virtual void test() {}
};
int main(int argc, char **argv)
{
A obj;
auto len = sizeof(obj);
std::cout << "sizeof(obj):" << len << std::endl;
char *p1 = reinterpret_cast<char *>(&obj);
char *p2 = reinterpret_cast<char *>(&obj.m_val);
if(p1 == p2)
{
std::cout << "vptr in obj end" << std::endl;
}
else
{
std::cout << "vptr in obj start" << std::endl;
}
return 0;
}
输出为:
sizeof(obj):16
vptr in obj start
即
obj
|---->|--------------------|
| vptr |
|--------------------|
| m_val |
|--------------------|
继承关系作用下虚函数的手工调用
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#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:
virtual void g()
{
std::cout << "Derive::g()" << std::endl;
}
};
int main(int argc, char **argv)
{
std::cout << "sizeof(Base):" << sizeof(Base) << std::endl;
std::cout << "sizeof(Derive):" << sizeof(Derive) << std::endl;
std::cout << "--------------------------------" << std::endl;
Derive *d = new Derive(); // 派生类指针
long *pvptr = (long *)d; // 指向对象的指针d转换成long *类型
long *vptr = (long *)(*pvptr); // (*pvptr)表示pvptr指向的对象,也就是Derive本身,Derive对象是8字节,代表的是虚函数表指针
for (int i = 0; i < 3; ++i)
{
printf("Derive vptr[%d] = %p\n", i, vptr[i]);
}
std::cout << std::endl;
using func = void (*)();
func df = (func)vptr[0];
func dg = (func)vptr[1];
func dh = (func)vptr[2];
df();
dg();
dh();
std::cout << "--------------------------------" << std::endl;
Base *b = new Base();
long *pvptrb = (long *)b;
long *vptrb = (long *)(*pvptrb);
for (int i = 0; i < 3; ++i)
{
printf("Base vptr[%d] = %p\n", i, vptrb[i]);
}
std::cout << std::endl;
func bf = (func)vptrb[0];
func bg = (func)vptrb[1];
func bh = (func)vptrb[2];
bf();
bg();
bh();
return 0;
}
运行结果为:
sizeof(Base):8
sizeof(Derive):8
--------------------------------
Derive vptr[0] = 0x5555555554fa
Derive vptr[1] = 0x5555555555b4
Derive vptr[2] = 0x555555555576
Base::f()
Derive::g()
Base::h()
--------------------------------
Base vptr[0] = 0x5555555554fa
Base vptr[1] = 0x555555555538
Base vptr[2] = 0x555555555576
Base::f()
Base::g()
Base::h()
即
虚函数表分析
- 一个类只有包虚函数才会存在虚函数表,同属于一个类的对象共享虚函数表,但是有各自的vptr(虚函数表指针),所指向的地址(虚函数表首地址)是相同的
- 父类中有虚函数就等于子类中有虚函数,换而言之,父类中有虚函数表,子类中则肯定有虚函数表,因为子类是继承自父类的。只要在父类中是虚函数,子类中即使不写virtual也是虚函数,但不管是父类还是子类都只会有一个虚函数表
- 如果子类中完全没有虚函数,则认为子类的虚函数表和父类的虚函数表类容相同。仅仅是类容相同,这2个表在内存中的位置不同,是类容相同的2张表。
多重继承虚函数表分析
一个对象,如果它的类有多个基类,则有多个虚函数表指针,在继承中,对应各个基类的vptr按继承顺序依次放在类的内存空间中,且子类与第一个基类共用一个vptr(第二个基类有自己的vptr)
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <iostream>
class Base1
{
public:
virtual void f()
{
std::cout << "Base1::f()" << std::endl;
}
virtual void g()
{
std::cout << "Base1::g()" << std::endl;
}
};
class Base2
{
public:
virtual void h()
{
std::cout << "Base2::h()" << std::endl;
}
virtual void i()
{
std::cout << "Base2::i()" << std::endl;
}
};
class Derive : public Base1, public Base2
{
public:
virtual void f()
{
std::cout << "Derive::f()" << std::endl;
}
virtual void i()
{
std::cout << "Derive::i()" << std::endl;
}
virtual void mh()
{
std::cout << "Derive::mh()" << std::endl;
}
virtual void mi()
{
std::cout << "Derive::mi()" << std::endl;
}
virtual void mj()
{
std::cout << "Derive::mj()" << std::endl;
}
};
int main(int argc, char **argv)
{
std::cout << "sizeof(Base1):" << sizeof(Base1) << std::endl;
std::cout << "sizeof(Base2):" << sizeof(Base2) << std::endl;
std::cout << "sizeof(Derive):" << sizeof(Derive) << std::endl;
std::cout << "--------------------------------" << std::endl;
Derive ins;
Base1 &b1 = ins;
Base2 &b2 = ins;
Derive &d = ins;
using func = void (*)();
long *pderive1 = (long *)(&ins);
long *vptr1 = (long *)(*pderive1); // 第一个虚函数表指针
long *pderive2 = pderive1 + 1; // 跳过8byte
long *vptr2 = (long *)(*pderive2); // 取第二个虚函数表指针
func f1 = (func)vptr1[0];
func f2 = (func)vptr1[1];
func f3 = (func)vptr1[2];
func f4 = (func)vptr1[3];
func f5 = (func)vptr1[4];
func f6= (func)vptr1[5];
func ff1 = (func)vptr2[0];
func ff2 = (func)vptr2[1];
b1.f();
b2.i();
d.f();
d.i();
d.mh();
d.g();
std::cout << "--------------------------------" << std::endl;
f1();
f2();
f3();
f4();
f5();
f6();
std::cout << "--------------------------------" << std::endl;
ff1();
ff2();
return 0;
}
输出如下
sizeof(Base1):8
sizeof(Base2):8
sizeof(Derive):16
--------------------------------
Derive::f()
Derive::i()
Derive::f()
Derive::i()
Derive::mh()
Base1::g()
--------------------------------
Derive::f()
Base1::g()
Derive::i()
Derive::mh()
Derive::mi()
Derive::mj()
--------------------------------
Base2::h()
Derive::i()
linux 下运行g++ -fdump-lang-class main.cpp(g++ -fdump-tree-gimple main.cpp生成gimple文件)生成a-main.cpp.001l.class文件,从里面可以找到
Vtable for Base1
Base1::_ZTV5Base1: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Base1)
16 (int (*)(...))Base1::f
24 (int (*)(...))Base1::g
Class Base1
size=8 align=8
base size=8 base align=8
Base1 (0x0x7f67cf7ed1e0) 0 nearly-empty
vptr=((& Base1::_ZTV5Base1) + 16)
Vtable for Base2
Base2::_ZTV5Base2: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Base2)
16 (int (*)(...))Base2::h
24 (int (*)(...))Base2::i
Class Base2
size=8 align=8
base size=8 base align=8
Base2 (0x0x7f67cf7ed6c0) 0 nearly-empty
vptr=((& Base2::_ZTV5Base2) + 16)
Vtable for Derive
Derive::_ZTV6Derive: 12 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI6Derive)
16 (int (*)(...))Derive::f
24 (int (*)(...))Base1::g
32 (int (*)(...))Derive::i
40 (int (*)(...))Derive::mh
48 (int (*)(...))Derive::mi
56 (int (*)(...))Derive::mj
64 (int (*)(...))-8
72 (int (*)(...))(& _ZTI6Derive)
80 (int (*)(...))Base2::h
88 (int (*)(...))Derive::_ZThn8_N6Derive1iEv
Class Derive
size=16 align=8
base size=16 base align=8
Derive (0x0x7f67cf806230) 0
vptr=((& Derive::_ZTV6Derive) + 16)
Base1 (0x0x7f67cf7ed7e0) 0 nearly-empty
primary-for Derive (0x0x7f67cf806230)
Base2 (0x0x7f67cf7ed840) 8 nearly-empty
vptr=((& Derive::_ZTV6Derive) + 80)
结论
- 子类对象ins有2个虚函数表指针vptr1,vptr2
- 类Derive有2个虚函数表,因为它继承了2个基类
- 子类和第一个基类共用一个vptr
- 子类中虚函数覆盖了父类中同盟的虚函数
虚函数之vptr、vtbl创建时机
vprt虚函数表指针什么时候创建?
vptr跟着对象走,所以对象什么时候创建出来,vptr就什么时候创建出来,即运行时创建。实际上对于有虚函数的类,在编译的时候,编译器会往相关的构造函数中增加为vptr赋值的代码,这是在编译期间编译器为构造函数所增加的代码。当程序运行时,遇到创建对象的代码,执行对象的构造函数,那么这个构造函数里有给对象的vptr赋值的语句,自然这个对象的vptr就被赋值了
虚函数表vtbl时什么时候创建的?
虚函数表(vtbl)是编译器在编译期间就为每个类确定好了对应的虚函数表vtbl的内容,然后也是在编译器编译期间在相应的类构造函数中添加vtbl赋值的代码,当程序运行时,运行到生成类对象代码时,会调用类的构造函数,执行到类的构造函数中的给vtbl赋值的代码,那么这个类对象的vtbl就有值了