C++虚继承的内存结构
2013-11-04 20:50
225 查看
我用vc2003观测到的实际情况是。在类中增加一个指针(VBPTR)指向一个VBTBL,这个VBTBL的第一项记载的是从VBPTR 与本类的偏移地址,如果本类有虚函数,那么第一项是FF FF FF FC(也就是-4),如果没有则是零,第二项起是VBPTR与本类的虚基类的偏移值。vc2003的这种方案个人觉得没有Bjarne的好,一是要多一个指针,二是因为VBPTR与虚函数表分开设计,也不便于修改。至于其它编译器,因为我跟其它编译器不熟,所以也就没有实测它们。
下面给出对于类定义
struct B1
{
int a;
int b;
};
struct B2
{
virtual void foo(void);
int c;
int d;
};
struct Test : virtual public B1, virtual public B2
{
virtual void func1(void);
virtual void func2(void);
virtual void func3(void);
int X;
};
一个Test 对象的内存布局图,我们可以清楚的看到在VS2003中VBPTR以及VBTBL的结构以及其相关的内容是什么意义。以及Bjarne的方案的优点。
最后我们来看一个完整的例子以及内存结构布局。图后有相关代码。
代码如下:
struct A
{
A(int v=100):X(v){};
virtual
void foo(void){}
int X;
};
struct B :virtualpublic A
{
B(int v=10):Y(v),A(100){};
virtual
void fooB(void){}
int Y;
};
struct C :
virtual public A
{
C(int v=20):Z(v),A(100){}
virtual
void fooC(void){}
int Z;
};
struct D :
public B, public C
{
D(int v =40):B(10),C(20),A(100),L(v){}
virtual
void fooD(void){}
int L;
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
int *ptr;
ptr = (int*)&a;
cout << ptr << " sizeof = " << sizeof(a) <<endl;
for(int i=0;i<sizeof(A)/sizeof(int);i++)
{
if(ptr[i] < 10000)
{
cout << dec << ptr[i]<<endl;
}
else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;
}
cout << "--------------------------------------" <<endl;
B b;
ptr = (int*)&b;
cout <<"addr:" << ptr << " sizeof = " <<
sizeof(b) <<endl;
for(int i=0;i<sizeof(B)/sizeof(int);i++)
{
if(ptr[i] < 10000)
{
cout << dec << ptr[i]<<endl;
}
else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;
}
cout << "--------------------------------------" <<endl;
D d;
ptr = (int*)&d;
cout <<"addr:" << ptr << " sizeof = " <<
sizeof(d) <<endl;
for(int i=0;i<sizeof(D)/sizeof(int);i++)
{
if(ptr[i] < 10000)
{
cout << dec << ptr[i]<<endl;
}
else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;
}
return 0;
}
另外看的一篇文章也很有启发性,大致内存结构也与上同
最近在研究虚继承,对带有虚函数的虚拟继承的内存布局有些疑惑,求大神指点,最好带图的,谢谢!
例如下面的代码:
class A
{
public:
A(){cout << "A called"<< endl;}
virtual void print(){cout << "A print" <<endl;}
};
class B :public virtual A
{
public:
B(){cout << "B called" << endl;}
void print(){cout << "B print" << endl;}
};
单个的虚继承没有意义,而且它的布局和普通的单继承完全一样。
虚继承主要用于多继承中的菱形继承,举例如下:
class Top
{
public:
int a;
};
class Left : virtual public Top
{
public:
int b;
};
class Right : virtual public Top
{
public:
int c;
};
class Bottom : public Left, public Right
{
public:
int d;
};
它的继承关系图如下:
可以得出 Bottom 的内存结构可能是这样子的。
从图中可以看出,Top 中的数据只保存了一份,也就是说,虚拟继承其实就是共享数据的意思。
但是虚继承的内存结构实际上没有这么简单,它应该是这样子的:
上图有两点值得大家注意。第一点就是类中成员分布顺序是完全不一样的(实际上可以说是正好相反)。第二点,类中增加了vptr指针,这些是被编译器在编译过程中插入到类中的(在设计类时如果使用了虚继承,虚函数都会产生相关vptr)。同时,在类的构造函数中会对相关指针做初始化,这些也是编译器完成的工作。Vptr指针指向了一个“virtual table”。在类中每个虚基类都会存在与之对应的一个vptr指针。
我对虚继承、普通继承中虚函数的内存放置总结成几句话:
1、虚继承时子与基需分开。子在前,基在后。(子类的虚函数与积累的虚函数需要分开,不可覆盖)
2、普通继承时,基在前,子在后。如果是多重继承,则顺序是基1-基2-最子类-最基类(子类的虚函数如果和基类相同,会覆盖子类的虚函数)
下面给出对于类定义
struct B1
{
int a;
int b;
};
struct B2
{
virtual void foo(void);
int c;
int d;
};
struct Test : virtual public B1, virtual public B2
{
virtual void func1(void);
virtual void func2(void);
virtual void func3(void);
int X;
};
一个Test 对象的内存布局图,我们可以清楚的看到在VS2003中VBPTR以及VBTBL的结构以及其相关的内容是什么意义。以及Bjarne的方案的优点。
最后我们来看一个完整的例子以及内存结构布局。图后有相关代码。
代码如下:
struct A
{
A(int v=100):X(v){};
virtual
void foo(void){}
int X;
};
struct B :virtualpublic A
{
B(int v=10):Y(v),A(100){};
virtual
void fooB(void){}
int Y;
};
struct C :
virtual public A
{
C(int v=20):Z(v),A(100){}
virtual
void fooC(void){}
int Z;
};
struct D :
public B, public C
{
D(int v =40):B(10),C(20),A(100),L(v){}
virtual
void fooD(void){}
int L;
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
int *ptr;
ptr = (int*)&a;
cout << ptr << " sizeof = " << sizeof(a) <<endl;
for(int i=0;i<sizeof(A)/sizeof(int);i++)
{
if(ptr[i] < 10000)
{
cout << dec << ptr[i]<<endl;
}
else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;
}
cout << "--------------------------------------" <<endl;
B b;
ptr = (int*)&b;
cout <<"addr:" << ptr << " sizeof = " <<
sizeof(b) <<endl;
for(int i=0;i<sizeof(B)/sizeof(int);i++)
{
if(ptr[i] < 10000)
{
cout << dec << ptr[i]<<endl;
}
else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;
}
cout << "--------------------------------------" <<endl;
D d;
ptr = (int*)&d;
cout <<"addr:" << ptr << " sizeof = " <<
sizeof(d) <<endl;
for(int i=0;i<sizeof(D)/sizeof(int);i++)
{
if(ptr[i] < 10000)
{
cout << dec << ptr[i]<<endl;
}
else cout << hex << ptr[i] <<" = " << hex << * ((int*)(ptr[i])) <<endl;
}
return 0;
}
另外看的一篇文章也很有启发性,大致内存结构也与上同
最近在研究虚继承,对带有虚函数的虚拟继承的内存布局有些疑惑,求大神指点,最好带图的,谢谢!
例如下面的代码:
class A
{
public:
A(){cout << "A called"<< endl;}
virtual void print(){cout << "A print" <<endl;}
};
class B :public virtual A
{
public:
B(){cout << "B called" << endl;}
void print(){cout << "B print" << endl;}
};
单个的虚继承没有意义,而且它的布局和普通的单继承完全一样。
虚继承主要用于多继承中的菱形继承,举例如下:
class Top
{
public:
int a;
};
class Left : virtual public Top
{
public:
int b;
};
class Right : virtual public Top
{
public:
int c;
};
class Bottom : public Left, public Right
{
public:
int d;
};
它的继承关系图如下:
可以得出 Bottom 的内存结构可能是这样子的。
从图中可以看出,Top 中的数据只保存了一份,也就是说,虚拟继承其实就是共享数据的意思。
但是虚继承的内存结构实际上没有这么简单,它应该是这样子的:
上图有两点值得大家注意。第一点就是类中成员分布顺序是完全不一样的(实际上可以说是正好相反)。第二点,类中增加了vptr指针,这些是被编译器在编译过程中插入到类中的(在设计类时如果使用了虚继承,虚函数都会产生相关vptr)。同时,在类的构造函数中会对相关指针做初始化,这些也是编译器完成的工作。Vptr指针指向了一个“virtual table”。在类中每个虚基类都会存在与之对应的一个vptr指针。
我对虚继承、普通继承中虚函数的内存放置总结成几句话:
1、虚继承时子与基需分开。子在前,基在后。(子类的虚函数与积累的虚函数需要分开,不可覆盖)
2、普通继承时,基在前,子在后。如果是多重继承,则顺序是基1-基2-最子类-最基类(子类的虚函数如果和基类相同,会覆盖子类的虚函数)
相关文章推荐
- 虚函数: 多重继承下派生类对象的内存结构
- C++反汇编第五讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式.
- c++虚继承的内存问题
- c++虚继承对象的内存布局
- 虚表结构与虚继承内存对象模型
- C++类对应的内存结构(虚函数,虚继承)
- 【C++】c++单继承、多继承、菱形继承内存布局(虚函数表结构)
- c++单继承、多继承、菱形继承内存布局(虚函数表结构)
- c++虚继承对象的内存布局
- 虚表结构与虚继承内存对象模型
- 多重继承的内存结构分析
- 继承和组合的内存结构异同分析
- c++虚继承对象的内存布局
- C++虚继承内存对象模型探讨
- c++类的内存结构(单继承)
- C++虚继承的内存模型
- c++面试题之 多继承内存结构
- 虚表结构与虚继承内存对象模型
- 【C++】c++单继承、多继承、菱形继承内存布局(虚函数表结构)
- c++单继承、多继承、菱形继承的内存布局(虚函数表结构)