一、单个类带虚函数的数据成员布局
class MYCLASS
{
public:
int m_i;
int m_j;
virtual void myvirfunc()
{
}
MYCLASS()
{
int abc = 1;
}
~MYCLASS()
{
int abc = 1;
}
};
void main()
{
cout << sizeof(MYCLASS) << endl;
printf("m_i:%d\n", &MYCLASS::m_i);
printf("m_j:%d\n", &MYCLASS::m_j);
system("pause");
}
结果:
类中引入虚函数时,会有额外的成本付出
1.编译的时候,编译器会产生虚函数表。
2.对象中会产生虚函数表指针vptr,用于指向虚函数表
3.增加或者扩展构造函数,增加给虚函数表指针vptr赋值的代码,让vptr指向虚函数表。
4.如果多重继承,比如你继承了两个父类,每个父类都有虚函数的话,每个父类都会有vptr,那继承时,子类就会把两根vptr都继承过来,如果子类还有自己额外的虚函数的话,子类与第一个基类共用一个vptr.
5.析构函数中也被扩展了虚函数表指针vptr相关的赋值代码,感觉这个赋值代码似乎和构造函数中代码相同。
下面是这个类的布局
下面是跟踪验证:
所以前面的四个字节一定是虚函数表的地址
二、单一继承父类带虚函数的数据成员布局
看代码:
class father
{
public:
int m_fi;
virtual void go(){};
};
class MYCLASS:public father
{
public:
int m_i;
int m_j;
virtual void myvirfunc()
{
}
MYCLASS()
{
int abc = 1;
}
~MYCLASS()
{
int abc = 1;
}
};
void main()
{
cout << sizeof(MYCLASS) << endl;
printf("m_i:%d\n", &MYCLASS::m_i);
printf("m_j:%d\n", &MYCLASS::m_j);
MYCLASS c;
c.m_i = 1;
c.m_j = 2;
system("pause");
}
结果:
结果为16,因为子类和父类共用一张表,也就是只有一个vptr.由偏移数,可以得到如下布局。
可见往后走了8个字节。
三、单一继承父类不带虚函数的数据成员布局
看如下代码:
class father
{
public:
int m_fi;
};
class MYCLASS:public father
{
public:
int m_i;
int m_j;
virtual void myvirfunc()
{
}
MYCLASS()
{
int abc = 1;
}
~MYCLASS()
{
int abc = 1;
}
};
void main()
{
cout << sizeof(MYCLASS) << endl;
printf("m_fi:%d\n", &MYCLASS::m_fi);
printf("m_i:%d\n", &MYCLASS::m_i);
printf("m_j:%d\n", &MYCLASS::m_j);
MYCLASS c;
c.m_i = 1;
c.m_j = 2;
system("pause");
}
结果:
从偏移结果,可能会得到如下结果(但是是不对的):
分析:
从结果可以得出如下布局图(m_bi就是m_fi)
那父类带virtual的偏移是4,不带的是0,布局也一样,那是如何访问的呢?
这是由于,当访问父类的m_bi的时候,会将this指针进行调整(+4个字节),这是编译器帮我们做的,加了之后也就是m_bi的地址了。一定要记住偏移只是偏移。