最近在写一个C++ 和java的socket通信程序,我把整个struct直接通过socket写出去,客户端需要解释接收到的字节数据,引申出了C++对象的布局问题,我读了《深入探索C++对象模型》的一些内容,结合自己的理解,整理如下。
C++中类的关键字有两个,struct和class,struct是为了和C语言兼容,class则是代表着面向对象思想。我记得《C++ Primer》一书里曾写过c++里struct和class是一样的,唯一的区别是struct的默认访问级别是public,而class是private。
C 语言也有struct,可以自己定义新的数据类型,但是它没有真正的面向对象机制(虽然可以以类似面向对象的思想编程)。C++的struct是面向对象的,也就是封装(数据隐藏),继承与多态。C语言的struct没有机制支持继承与多态。
先说明什么是内存对齐,许多计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,k称为对齐模数,这就是所谓的内存对齐。如果一种对齐方式的k是另一种的整数倍(>1),那么k较大者的对齐要求更为严格,因为这时可以放置的位置更少。不同的编译器对于模数的设定方案未必相同,不同类型的类型的模数也往往不同。微软的win32编译器,基本数据类型的对齐模数是该类型的大小,sizeof(primarytype),编译器的对齐选项也可以设定。(本段摘自http://blog.csdn.net/soloist/article/details/213717)
关于C struct的内存布局
可以看做C++对象布局规则的“一部分”,和C++ struct去掉为了支持面向对象的开销的部分是一致的。
我的理解,C struct就是几个数据“攒”在一起,组成一个整体,没有其他的东西
比如struct point{int x,y,z;} p; 在内存中p就是3个int值,“攒”在一起,没有其他的数据。
弄清楚布局就要确定大小和位置,有几条标准规定了这个问题
ANSIC标准保证struct中的各个字段在内存中的位置随着他们的声明顺序依次递增。
ANSIC规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。填充区保证struct的每个字段都能符合各自的内存对齐要求。
ANSIC标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,可以更严格。这个是说的整个结构体的首地址。
所以虽然ANSI C标准做出了一些规定,但是对于对齐数据部分,还是取决于编译器,所以同样的struct在不同的编译器下/不同的编译对齐选项参数,其内存布局可能不同。
假设int类型长4个字节,char类型1个字节,short类型2个字节,各类型按照各自的大小作为对齐模数,struct的整体对齐按照int大小作为对齐模数,内存布局例子如下
C++对象的内存布局
一个C++类包含类数据成员(class data member)和类方法成员(class function member),数据成员包括静态(static)数据成员和非静态数据(nonstatic)成员,类成员方法包括静态(static),非静态(nonstatic)和虚拟(virtural)
C++在struct(class)上的布局成本和C语言布局成本发生不同的情况出现在virtural。
也就是说,一个没有virtural function,virtural base class的C++ struct(class)和C struct的内存布局是一样的。C ++ struct(class)里面的 function并不会出现在类的实例中,构造函数,析构函数,操作符重载只要不含virtural就和只含有这几个基本数据的C struct是一样的(这里有个条件,C++ struct里的静态数据成员不会出现在实例中,并且没有考虑嵌套其他struct的情况,只考虑了数据成员为基本数据类型的情况)。
typedef struct point_3d { int x; int y; int z; } p;//C语言的struct struct point_3d{//C++语言的struct public: point_3d(){} point_3d(int a, int b, int c):x(a),y(b),z(c){} ~point_3d(){} void unit(){ x= x /sqrt(x*x+y*y+z*z); y = y/sqrt(x*x+y*y+z*z); z = z/sqrt(x*x+y*y+z*z); } private: int x; int y; int z; };
C++对象的内存布局规则
1. 非静态数据成员被配置于每一个对象实例(class object)之内,静态数据成员被存放在所有的对象实例之外。
2. 静态和非晶态的方法成员也存放在所有的对象实例之外。
3. 虚拟方法virtural function以两个步骤支持
3.1. 每一个类class产生出一堆指向virtual functions的指针,存放在表格中,称为virtual table (vtbl)
3.2. 每一个对象实例被添加了一个指针,指向相关的virtual table。这个指针通常称为vptr。
Vptr的设定setting和重置resetting由每一个类的构造器,析构器,赋值运算符自动完成。
每一个类所关联的type_info object也经由virtural table 指出,通常放在表格的第一个slot处。
举例
class Point{ public: Point(float xval); virtual ~Point(); float x() const; static int PointCount(); protected: virtual ostream& print(ostream &os)const; float _x; static int _point_count; };
内存布局图如图所示(图片源自《深入探索C++对象模型》一书)
关于继承
如果把继承也考虑在内,问题又会复杂很多,这里就简单描述一下,具体可以阅读《深入探索C++对象模型》这本书。
C++可以单一继承,多继承,虚拟继承(基类在继承串链不论被派生多少次,都会只含有一个实例)
C++最初实现时是直接把父类对象包含进来,后来实现有了些变化,有的是在加上一个bptr指针,指向一个父类对象表,也有的是在vtbl里实现,但是一定是在数据成员部分的后面。
所以,
数据成员的那一部分和C struct的规则是一致的,所以这一部分参照C struct的内存布局即可,
没有虚拟方法的C++ struct最后是不含有vptr的,含有虚拟方法的struct才会加上这一项。
说明:
本文由giantpoplar发表于CSDN文章地址 http://blog.csdn.net/giantpoplar/article/details/47658679
转载请保留本说明
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/giantpoplar/article/details/47658679