#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.4549
void 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;
}