这篇文章我要简单地讲解下c++对象的内存布局,虽然已经有很多很好的文章,不过通过实现发现有些地方不同的编译器还是会有差别的,希望和大家交流。
在没有用到虚函数的时候,C++的对象内存布局和c语言的struct是一样的,这个比较容易理解,本文只对有虚函数的情况作分析,大致可以从以下几个方面阐述,
1. 单一继承
2. 多重继承
3. 虚继承
下面循序渐进的逐个分析,环境是ubuntu 12.04.3 LTS+gcc4.8.1
单一继承
为了实现运行时多态,虚函数的地址并不能在编译期决定,需要运行时通过虚函数表查找实际调用的虚函数地址。单一继承主要要弄明白两个问题:
1. 虚函数表在哪里?
2. 基类和派生类的虚函数是按照怎样的规则组织的?
类结构代码如下:
class base{
public:
base(int bv):bval(bv){};
virtual void base_f(){cout<<"base::f()"<<endl;}
virtual void base_g(){cout<<"base::g()"<<endl;}
private:
int bval;
}; class derived: public base{
public:
derived(int bv, int dv):base(bv), dval(dv){};
void base_f(){cout<<"derived::f()"<<endl;}
virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
int dval;
};
使用以下测试代码查看基类和派生类的内存空间:
base b();
FUN fun;
int **pvtab = (int**)&b;
cout<<"[0]:base->vptr"<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 2 "<<pvtab[][]<<endl;
cout<<"[1]:bval "<<(int)pvtab[]<<endl;
derived d(, );
pvtab = (int**)&d;
cout<<"[0]:derived->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 3 "<<pvtab[][]<<endl;
cout<<"[1]:bval "<<(int)pvtab[]<<endl;
cout<<"[2]:dval "<<(int)pvtab[]<<endl;
运行结果:
[0]:base->vptr
0 base::f()
1 base::g()
2 1919247415
[1]:bval 10
[0]:derived->vptr
0 derived::f()
1 base::g()
2 derived::h()
3 0
[1]:bval 10
[2]:dval 100
用图表示就是这样的(本文中属于同一个类的函数和数据成员我都用同一种颜色表示,以便区分)。
结果可以看出:
1. 虚函数表指针在对象的第一个位置。
2. 成员变量根据其继承和声明顺序依次排列。
3. 虚函数根据继承和声明顺序依次放在虚函数表中,派生类中重写的虚函数在原来位置更新,不会增加新的函数。
4. 派生类虚函数表最后一个位置是NULL,但是基类是一个非空指针(红色字体显示),我不明白这个位置在gcc中的作用,有知道的朋友可以告诉我,非常感谢!
多重继承
如果加上多重继承,情况会有那么一点点复杂了,多个基类的虚函数如何组织才能使用任意一个基类都可以达到多态效果?
类结构代码如下:
class base1{
public:
base1(int bv):bval1(bv){};
virtual void base_f(){cout<<"base1::f()"<<endl;}
virtual void base1_g(){cout<<"base1::g()"<<endl;}
private:
int bval1;
}; class base2{
public:
base2(int bv):bval2(bv){};
virtual void base_f(){cout<<"base2::f()"<<endl;}
virtual void base2_g(){cout<<"base2::g()"<<endl;}
private:
int bval2;
}; class derived: public base1, public base2{
public:
derived(int bv1, int bv2, int dv):base1(bv1),base2(bv2), dval(dv){};
virtual void base_f(){cout<<"derived::f()"<<endl;}
virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
int dval;
};
测试代码查看内存布局:
derived d(, , );
FUN fun;
int **pvtab = (int**)&d;
cout<<"[0]:base1->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 3 "<<pvtab[][]<<endl;
cout<<"[1]:bval1 "<<(int)pvtab[]<<endl;
cout<<"[2]:base2->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 2 "<<pvtab[][]<<endl;
cout<<"[3]:bval2 "<<(int)pvtab[]<<endl;
cout<<"[4]:dval "<<(int)pvtab[]<<endl;
运行结果如下:
[0]:base1->vptr
0 derived::f()
1 base1::g()
2 derived::h()
3 -8
[1]:bval1 10
[2]:base2->vptr
0 derived::f()
1 base2::g()
2 0
[3]:bval2 100
[4]:dval 1000
总结:
1. 每个基类有单独的虚函数表,子类的虚函数放在第一个基类的虚函数表中。
2. 基类内存空间按基类的声明顺序依次排列
3. 如果多个基类之间有同名虚函数,派生类重写时会覆盖所有基类的该函数。这里插一句,如果没有覆盖,不同基类之间的同名函数是会产生二义性的,使用时必须指定哪个类的函数。
4. 第一个基类的虚函数表最后一个位置仍不为空(红色字体),作用不明。
虚继承
虚继承需要保证基类在派生类中只有一份,所以内存布局比多重继承又复杂那么一点点,我们以最典型的菱形继承为例子介绍。
类结构代码如下
class base{
public:
base(int bv):bval(bv){};
virtual void base_f(){cout<<"base::f()"<<endl;}
virtual void base_t(){cout<<"base::t()"<<endl;}
private:
int bval;
}; class base1:virtual public base{
public:
base1(int bv, int bv1):base(bv), bval1(bv1){};
void base_f(){cout<<"base1::f()"<<endl;}
virtual void base1_g(){cout<<"base1::g()"<<endl;}
virtual void base1_k(){cout<<"base1::k()"<<endl;}
private:
int bval1;
}; class base2: virtual public base{
public:
base2(int bv, int bv2):base(bv), bval2(bv2){};
void base_f(){cout<<"base2::f()"<<endl;}
virtual void base2_g(){cout<<"base2::g()"<<endl;}
virtual void base2_k(){cout<<"base2::k()"<<endl;}
private:
int bval2;
}; class derived: public base1, public base2{
public:
derived(int bv, int bv1, int bv2, int dv):base(bv), base1(bv, bv1),base2(bv, bv2), dval(dv){};
void base_f(){cout<<"derived::f()"<<endl;}
void base1_g(){cout<<"derived::base1_g()"<<endl;}
void base2_g(){cout<<"derived::base2_g()"<<endl;}
virtual void derived_h(){cout<<"derived::h()"<<endl;}
private:
int dval;
}
测试代码查看内存结构
derived d(, , , );
FUN fun = NULL;
int **pvtab = (int**)&d;
cout<<"[0]:base1->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 5 "<<pvtab[][]<<endl;
cout<<"[1]:bval1 "<<(int)pvtab[]<<endl; cout<<"[2]:base2->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 3 "<<pvtab[][]<<endl;
cout<<"[3]:bval2 "<<(int)pvtab[]<<endl;
cout<<"[4]:dval "<<(int)pvtab[]<<endl;
cout<<"[5]:base->vptr "<<endl;
for(int i=; i<; i++){
fun = (FUN)pvtab[][i];
cout<<" "<<i<<" ";
fun();
}
cout<<" 2 "<<pvtab[][]<<endl;
运行结果:
[0]:base1->vptr
0 derived::f()
1 derived::base1_g()
2 base1::k()
3 derived::base2_g()
4 derived::h()
5 134516512
[1]:bval1 100
[2]:base2->vptr
0 derived::f()
1 derived::base2_g()
2 base2::k()
3 0
[3]:bval2 1000
[4]:dval 10000
[5]:base->vptr
0 derived::f()
1 base::t()
2 134517004
结果可以看出:
1. 直接非虚基类按声明顺序依次排列,然后是派生类,这个和多重继承是一样的。
2. 派生类的虚函数仍然放在第一个直接非虚基类的虚函数表中.
3. 虚基类放在最后,只有一份。