Data语意学

Data Member的内存布局:
前言知识:
C++ standard要求:同一个access section中,members的排列只需符合“较晚出现的members在class object中有较高的地址”,也就是members之间可能会出现边界调整的字节填充等。另外编译器还可能会合成内部一些使用的data members比如vptr,但是编译器会讲vptr放在什么位置呢?开头最后?不同的编译器有不同的摆放位置,C++ Standard对于布局所持的是放任的态度。
Vs2008 和g++编译器都是将vptr放在object的开头,验证代码:
class Base {
public:
 virtual void f() {
  cout << "Base::f" << endl;
 }
};
typedef void(*Fun)(void);
int main()
{
 Base base;
 cout << "vptr:" << (int*)&base << endl; // vptr放在object前端
 cout << "虚函数表中第一个函数地址:" << (int*)*((int*)&base) + 0<< endl;
 Fun pfun = (Fun)*((int*)*((int*)&base) + 0); 
 pfun(); // Base::f
 return 0;
}

本文从一下几个方面讲述C++的数据布局:(C++ Standard对于布局所持的是放任的态度,不同编译器之间会有差异,下面测试代码仅在vs2008和g++编译器,)
1) 单一继承而且没有多态时的数据布局
2) 单一继承加上多态时(virtual function)的数据布局
3) 多重继承
4) 虚拟继承

1. 单一继承而且没有多态时的数据布局
派生类从基类继承了数据,此时数据布局
class Base {
public:
 Base(): _x(1), _y(2) {}
 int _x;
 int _y;
};
class Derived : public Base{
public:
 Derived():_z(3){}
 int _z;
};
int main()
{
 Derived d;
 cout << "派生类object中_x的地址:" << (int*)&d<<endl;   // 0012FF58
 cout << "派生类object中_y的地址:" << (int*)&d + 1 <<endl;  // 0012FF5c
 cout << "派生类object中_z的地址:" << (int*)&d + 2 <<endl;  // 0012FF60
 cout << "派生类object中_y的值" << *((int*)&d + 0) << endl;  // 1
 cout << "派生类object中_y的值" << *((int*)&d + 1) << endl; // 2
 cout << "派生类object中_z的值" << *((int*)&d + 2) << endl; // 3
 return 0;
}



注意考虑字节对齐问题:
class Concrete1 {
private:
int val;
char bit1;
}
class Concrete2: public Concrete1 {
private:
char bit2;
}
Concrete1 object大小8字节
Concrete2 object大小是12字节 // 8 + 4

2) 单一继承加上多态时(virtual function)的数据布局
class Base {
public:
 Base(): _x(1), _y(2) {}
 virtual void foo_1() { cout << "Base::foo_1" << endl; }
 virtual void foo_2() { cout << "Base::foo_2" << endl; }
 int _x;
 int _y;
};
class Derived : public Base{
public:
 Derived():_z(3){}
 virtual void foo_2(){ cout << "Derived::foo_2" << endl; }
 virtual void foo_3(){ cout << "Derived::foo_3" << endl; }
 int _z;
};
typedef void(*Fun)(void);
int main()
{
Fun pfun;
 Base b;
 pfun = (Fun)*((int*)*((int*)&b) + 0);
 pfun();
 pfun = (Fun)*((int*)*((int*)&b) + 1);
 pfun();
 Derived d;
 pfun = (Fun)*((int*)*((int*)&d) + 0);
 pfun();
 pfun = (Fun)*((int*)*((int*)&d) + 1);
 pfun();
 pfun = (Fun)*((int*)*((int*)&d) + 2);
 pfun();
 return 0;
}
输出结果:
Base::foo_1;
Base::foo_2;
Base::foo_1;
Derived::foo_2;
Derived::foo_3;
内存图如下:


3) 多重继承
class Base {
public:
 Base(){}
 virtual void foo_1() { cout << "Base::foo_1" << endl; }
 virtual void foo_2() { cout << "Base::foo_2" << endl; }
};
class Derived : public Base{
public:
 Derived():_z(3){}
 virtual void foo_2(){ cout << "Derived::foo_2" << endl; }
 virtual void foo_3(){ cout << "Derived::foo_3" << endl; }
 int _z;
};
class BaseOther {
public:
 BaseOther(): _val(4) {}
 virtual void foo_other() { cout << "BaseOther::foo_other" << endl; }
 int _val;
};
class DerivedDeep : public Derived, public BaseOther{
public:
 DerivedDeep():_m(5){}
 int _m;
};
typedef void(*Fun)(void);
int main()
{
 Fun pfun;
 DerivedDeep d;
 int** ptr = (int**)&d;
 cout << "1) Derived vptr->vtable:" << endl;
 pfun = (Fun)ptr[0][0];
 pfun();
 pfun = (Fun)ptr[0][1];
 pfun();
 pfun = (Fun)ptr[0][2];
 pfun();
 cout << "2) Derived::_z == ";
 cout << (int)ptr[1] << endl << endl;
 cout << "3) BaseOther vptr->vtable:" << endl;
 pfun = (Fun)ptr[2][0];
 pfun();
 cout << "4) BaseOther::_val == ";
 cout << (int)ptr[3] << endl << endl;
 cout << "5) DerivedDeep::_m == ";
 cout << (int)ptr[4] << endl << endl;
 return 0;
}
运行结果:
内存结构图:

可以看出:

1)  每个父类都有自己的虚表。

2)  子类的成员函数被放到了第一个父类的表中。

3)  内存布局中,其父类布局依次按声明顺序排列。(再次说明C++ standard并未明确规定顺序,不同编译器可以有不同的顺序实现,本文代码只在g++和vs2008编译器)

4)  每个父类的虚表中的foo_2()函数都被overwrite成了子类的foo_2()(关系见Derived和Base)。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。


DerivedDeep pdd;

Derived* p_d = &pdd; // 只是简单的拷贝地址就好了,编译器不需要内部转换

BaseOther* p_bo = &pdd ;  // 编译器在此处做内部转化  p_bo  = ( BaseOther *)((char*)&pdd + sizeof(Derived));

如果是下面的情况:

DerivedDeep* pdd;

BaseOther* p_bo pdd;

// p_bo  = ( BaseOther *)((char*)pdd + sizeof(Derived));这样的转化是不够的,如果pdd为0,则p_bo将获得siezof(Derived)的大小

// 编译器转化的时候会加一个测试条件p_bo  = pdd ? ( BaseOther *)((char*)pdd + sizeof(Derived)) : 0;

 虚拟继承

Class 如果内含一个或多个virtual base class subobjects,讲被分割成两部分:一个不变区域和一个共享区域。不变区域中的数据,不管后继如何演化,总是拥有固定的offset(从object的开头算起),所以这一部分数据可以直接存取。至于共享区域,所表现的就是virtual base class subobject.这一部分的数据会因为每次的派生操作而有所变化,所以他们只能被间接存取,(各家编译器实现技术差异在间接存取上也表现不同)。

 VS编译器采用virtual base class table strategy:使用虚基类表,每一个class object如有一个或多个virtual base classes,就会在编译器中安插一个指针指向virtual base class table,真正的virtual base class 指针放在此表中

而GCC编译器使用virtual table offset strategy:将直接在派生类对象地址上加上一个常数,获得虚基类实例的地址,多重继承的时候

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值