2018.9.18更新
关于虚继承的sizeof问题
#include <iostream>
#include <vector>
#include <string.h>
#include <stdio.h>
using namespace std;
class A
{
public:
char a[4];
virtual void aa() {}
//const int Size;
};
class A2
{
public:
char a2[4];
virtual void aa2() {}
};
class B :virtual public A
{
char j[3];
public:
virtual void bb() {}
};
class C :public A
{
char k[3];
public:
virtual void cc() {}
};
class D :public virtual B
{
char i[3];
public:
virtual void cc() {}
};
class E :public virtual A, public virtual A2
{
char q[4];
public:
virtual void ee() {}
};
int main()
{
A a;
B b;
C c;
D d;
E e;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
cout << sizeof(d) << endl;
cout << sizeof(e) << endl;
}
本来以为自己很清楚这块了,结果发现完全不是这么回事。。。。。。
当发生虚继承时,内存模型(/d1reportSingleClassLayoutB ,/d1reportSingleClassLayoutE)会发生很大的差异。下图中vfptr是virtual funtion ptr,vbptr是virtual base ptr(虚基类指针)。
可以看到,B的内存中首先是自己的虚基类指针以及虚表指针,然后是char j[3],<alignment member>为占位符,接下来是虚基类A,A里面首先是vfptr,然后是char a[4],此时没有占位符。
1>class B size(20):
1> +---
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | j
1> | <alignment member> (size=1)
1> +---
1> +--- (virtual base A)
1>12 | {vfptr}
1>16 | a
1> +---
1>
1>class E size(28):
1> +---
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | q
1> +---
1> +--- (virtual base A)
1>12 | {vfptr}
1>16 | a
1> +---
1> +--- (virtual base A2)
1>20 | {vfptr}
1>24 | a2
1> +---
1>
虚基类E的内存模型如上图所示。
*****************************************************************************************************************************************
最近面试的时候被问了很多关于虚函数表的问题,大多数集中在如何通过虚函数表调用虚函数这种问题上,问得比较实在。
几个关键点:
1.每一个类对应的实例都会分配一个虚函数表,这个表的地址是分配在这个类所在内存地址的最前面:
#include<stdio.h>
class A
{
public:
int n;
private:
virtual void f0() { printf("the virtual function:f0()");}
virtual void g0() { printf("the virtual function:g0()");}
};
class B: public A
{
virtual void f0() { printf("the Derived function:f0()");}
};
typedef void(*Fun)(void);
int main()
{
A a;
B b;
printf("%x",(long*)(&b));
printf("\n%x\n",&(b.n));
printf("%x",*(long*)(&b));
Fun pfun = NULL;
pfun = (Fun)(*((long*)(*(long*)(&b))+0));
pfun();
printf("\n%d %d",sizeof(int*),sizeof(long*));
}
743d50d0
743d50d8
这一点可以通过打印类B的地址和类B中第一个成员变量n的地址看出,他们相差了8,这正好是一个虚函数表指针的大小(64位系统)。
2.如何通过虚函数表访问类中的虚函数,甚至开始越权访问
这里涉及到的知识点主要是函数指针,https://www.cnblogs.com/hushpa/p/5707475.html网上很多的文章都是以前比较老的时候写的了,这里涉及到一个问题,如果使用的是64位系统,对&b强制转换时如果转成了(int*)是会报错的。
如果非要用,则必须开启g++ -m32选项,具体参考https://blog.youkuaiyun.com/dlfxjc2/article/details/78624554。
但其实这里的原因我也不是非常清楚,这里待保留。
通过如下的代码,可以越权访问到本来不对子类开发的基类中private虚函数:
pfun = (Fun)(*((int*)(*(int*)(&b))+1));
fff4993c
fff49940
5658feb8the virtual function:g0()
PS:此结果是在-m32 选项下编译的,故可以看到&n和虚函数表地址之间只差了4。