本文共 4493 字,大约阅读时间需要 14 分钟。
#include <iostream>using std::cout;using std::endl;#include <iomanip>using std::setw;using std::left;// 数据对齐// 环境: windows 2003 + Intel Celeron CPU 2.53GHz + VC6.0// 资源参考:《Linux 内核设计与实现 第二版》ISBN:7-111-17865-3/TP.4549void sizeofClass();void main(){ cout <<left<<setw(20)<<"sizeof(bool)" <<sizeof(bool)<<endl; // 1 cout <<left<<setw(20)<<"sizeof(char)" <<sizeof(char)<<endl; // 1 cout <<left<<setw(20)<<"sizeof(short)" <<sizeof(short)<<endl; // 2 cout <<left<<setw(20)<<"sizeof(int)" <<sizeof(int)<<endl; // 4 cout <<left<<setw(20)<<"sizeof(long)" <<sizeof(long)<<endl; // 4 cout <<left<<setw(20)<<"sizeof(float)" <<sizeof(float)<<endl; // 4 cout <<left<<setw(20)<<"sizeof(double)" <<sizeof(double)<<endl; // 8 //cout <<setw(20)<<"sizeof(long long)"<<sizeof(long long)<<endl; cout <<left<<setw(20)<<"sizeof(long double)" <<sizeof(long double)<<endl; // 8 cout <<left<<setw(20)<<"sizeof(void *)" <<sizeof(void *)<<endl; // 4 (指针在32位系统上占4个字节) struct AAA{ double aDouble; /* 8 bytes */ int aInt; /* 4 bytes */ char aChar; /* 1 byte */ }; struct BBB{ char aChar; double aDouble; int aInt; }; cout << sizeof(AAA)<<" "<<sizeof(BBB)<<endl; // 打印 多少 ??? //sizeofClass(); //测试类的内存表示, 感兴趣的可以打开看看结果 } /* 数据对齐自然对齐:如果一个变量的内存地址正好是它长度的整倍数,它就被称为自然对齐。 一些体系结构对对齐要求非常严格。通常想基于RISC的系统, 载入未对齐数据会导致处理器陷入(一种可处理的错误)。还有一些系统可以访问没有对齐的数据,只不过性 能会下降。编写可移植性高的代码要避免对齐问题,保证所有的类型都能够自然对齐。 避免对齐引发的问题 一个数据类型长度比较小,它本来是对齐的,如果你用一个指针进行类型转换,并且 转换后的数据类型较长,那么通过改指针进行数据访问时就会引发对齐问题。也就是说, 下面的代码是错误的: char dob[10]; char *p = &dog[1]; unsigned long l = *(unsigned long *)p; 这个例子将一个指向char型的指针当作指向unsigned long型的指针来用,这会引起问 题,因为此时会试图从一个并不能被4整除的内存地址上载入32的unsigned long型数据。 非标准类型的对齐 前面提到了,对于标准数据类型来说,它的地址只要是其长度的整数倍就对齐了。而非标准的(复合的)C数据类型按照下列原则对齐: 1〉对于数组,只要按照基本数据类型进行对齐就可以了(其后的所有元素自然都能够对齐了)。 2〉对于联合,只要它包含的长度最大的数据类型能够对齐就可以了。 3〉对于结构体,只要它包含的长度最大的数据类型能够对齐就可以了。结构体还要引入 填补机制,这会引发下一个问题。 结构体填补 为了保证结构体中每一个成员都能够自然对齐,结构体需要被填补。举例:上面的代码中对于结构体 BBB 的定义如下: struct BBB{ char aChar; // 1 byte double aDouble; // 8 bytes int aInt; // 4 bytes}; 由于该结构体不能准确地满足各个成员自然对齐,所以它在内存中可不是按照原样存放的。编译器会在内存中创建一个类似下面的给出的结构体: struct BBB{ char aChar; // 1 byte u8 __pad0[7]; // 7 bytes double aDouble; // 8 bytes int aInt; // 4 bytes u8 __pad1[4]; // 4 bytes }; 填补的变量都是为了让数据自然对齐而加入的。__pad0 是为了让 aDouble 能够自然对齐而加入的,而其他额外的填补如 __pad1 则是为了让结构体的长度能够被最大元素(aDouble)的长度 8 整除而加入的。结构体AAA的长度为16,而不是13就是因为后一种填补引起的。通常你可以通过重新排列结构中的对象来避免填充或减少填充。像结构体AAA那样,把元素按长度的大小递减/增的排列后就可以使它占最小的空间了。注意:编译器,优化器并不能改变结构体中元素的排列次序。如ANSI C就明确规定不允许编译器改变结构体内成员对象的次序,它总是由你--程序员来决定。并不是所有的结构体进行这样的调整的,比如:该结构体作为一个标准的一部分,或者它是现有代码的一部分。 */// 以下是解释C++中类的内存表示机制void sizeofClass(){ class Father{ private: double aDouble; /* 8 bytes */ int aInt; /* 4 bytes */ public: char aChar; /* 1 byte */ // 为了print函数指针能够自然对齐 这里自动填补 3 个字节 virtual void print()const=0; // 4 bytes 函数指针 int getInt(){ // 4 bytes 函数指针 return aInt; } }; //虽然Son_0没有声明成员变量,但是所占大小仍然为 24 = 16 + 4 +4 //是因为父类的成员变量都在子类中保留,但是根据权限 //描述符而确定是否可以访问,父类中相应的成员函数也在子类中保留, //若有新的实现则指向新的代码段,访问权限仍有权限描述符确定。 class Son_0:public Father{ //这里仍然保留父类的数据段,故占 8 + 4 + 1 + 3 = 16 bytes public: //这里仍然保留着父类 getInt 函数指针,故占4个字节,若被重新实现则指向不同代码段 double getDouble(){ // 4 bytes 函数指针 return (double)aChar; } }; /* 虽然Son_1public继承了Father的数据成员,但仍然可以再声明一个 public char aChar,因为保留的空间位置不一样。 */ class Son_1:public Father{ public: char aChar; // 1 byte //为了aDouble能自然对齐,这里自动填补 7 个字节 private: double aDouble; // 8 bytes int aInt; // 4 bytes //为了print函数指针能自然对齐,这里自动填补 4 个字节 public: void print(){ // 4 bytes 函数指针 cout <<aChar<<endl; } }; cout << sizeof(Father) <<" "<< sizeof(Son_0)<<" "<< sizeof(Son_1)<<endl; }
转载地址:http://nhntb.baihongyu.com/