前言:最近在看这方面的东西,有所感触,所以写写,如果有不正确或者不完善的地方,还望不吝赐教。
一、背景知识
1.什么是字节对其齐?
现代计算机中内存空间基本都是按字节(Byte)为单位进行划分的,从理论上看,对任何数据类型的存储和访问都可以从任意地址开始,但实际实现中,为了访问速率的提升,通常按特定的规则来排列和存储数据,读取时在从相应的地址读取。而这其中的规则就是对齐。
2.字节对其有什么好处?
要理解字节对其的好处,就得先了解计算机的寻址方式。比如有些平台每次读都是从偶地址开始,那么对于一个int型(假设为32位系统),如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。在这种情况下,字节对其就能保证每个int的存储都是从偶地址开始,亦即每次读取一个int都只需要一个读周期。
二、字节对齐
不同操作系统里默认的对其方式存在差别,不过我见过貌似都是8 Byte对齐的,可以通过#pragma pack(n)来设定按n字节对其。
例:
#pragma pack(2) #pragma pack(4)
struct B struct C
{ {
char b; char b;
int a; int a;
short c; short c;
} }
针对上面两个列子,
对于结构体B:
数据成员b自身的对其值为1,指定对其值为2,故有效对其值为1(min(2,1)),假设存放地址为0x0000;
数据成员a自身对其值为4,指定对其值为2,故有效对其值为2(min(2,4)),存放地址的起始地址%2==0,故存放地址应该是:0x0002-0x0005
数据成员c自身对其值为2,指定对其值为2,故有效对其值为2(min(2,2)),存放地址的起始地址%2==0,故存放地址应该是:0x0006-0x0007
总的内存消耗为8。
结构体B自身对其值为4(max(1,2,4)),指定对其值为2,故结构体B有效对其值为2(min(2,4)),总内存消耗8%2==0,不需要补充。
对于结构体C:
数据成员b自身的对其值为1,指定对其值为4,故有效对其值为1(min(4,1)),假设存放地址为0x0000;
数据成员a自身对其值为4,指定对其值为4,故有效对其值为4(min(4,4)),存放地址的起始地址%4==0,故存放地址应该是:0x0004-0x0007
数据成员c自身对其值为2,指定对其值为4,故有效对其值为2(min(2,4)),存放地址的起始地址%2==0,故存放地址应该是:0x0008-0x0009
总的内存消耗为10。
结构体B自身对其值为4(max(1,2,4)),指定对其值为4,故结构体B有效对其值为4(min(4,4)),总内存消耗10%4!=0,需要最少补充2个字节(由此可看出字对齐的弊端——内存浪费,但就换取的效率而言,这还是值得的),故总的内存消耗为12字节。
故:sizeof(B)=8, sizeof(C)=12.
三、内存分配
1. 栈上内存分配(静态内存分配)
由于计算机中用于静态内存分配的地址空间有限(一般为1M或者2M),所以栈上地址分配是从高地址向低地址分配(但在具体的某个结构内部是从低到高)。下面通过共用体和结构体进行说明。
union:
union data1
{
double d;
int i;
char c1;
char c2[9];
};
int main(int argc,char **argv)
{
data1 d1;
d1.c2[0] = 10;
d1.c2[1] = 1;
d1.c2[2] = 1;
d1.c2[3] = 10;
cout <<"sizeof short union data1: "<<sizeof(data1)<<endl;
cout <<"sizeof short int: "<<sizeof(int)<<endl;
cout << d1.d << endl;
cout << d1.i << endl;
cout << d1.c1 << endl;
cout << (size_t)&d1 << endl;
cout << (size_t)&d1.i << endl;
cout << (size_t)&d1.c1 << endl;
cout << (size_t)&d1.c2<< endl;
cout << (size_t)&d1.c2[0]<< endl;
cout << (size_t)&d1.c2[1]<< endl;
cout << (size_t)&d1.c2[2]<< endl;
cout << (size_t)&d1.c2[3]<< endl;
cout <<endl;
system("pause");
return 0;
}
结果为:
说明:所设对齐方式为#pragma pack(8);对于共用体,内存地址从起始地址往高地址分配,也就是在内部是从低到高进行分配的。(d显示不正常是因为高32位没有赋值,c1无显示的原因不太清楚,望高人人指点)。
struct:
struct data1
{
double d;
int i;
char c1;
char c2[9];
};
int main(int argc,char **argv)
{
data1 d1;
cout <<"sizeof struct data1: "<<sizeof(d1)<<endl;
cout << (size_t)&d1 << endl;
cout << (size_t)&d1.d << endl;
cout << (size_t)&d1.i << endl;
cout << (size_t)&d1.c1 << endl;
cout << (size_t)&d1.c2 << endl;
cout << (size_t)&d1.c2[0] << endl;
cout << (size_t)&d1.c2[1] << endl;
cout <<endl;
system("pause");
return 0;
}
结果:
2. 堆上内存分配(动态内存分配)
计算机中动态内存的分配是在堆上进行(这个有没有大小限制以及上限为多少不太清楚),地址分配从低地址往高地址分配。
四、数据存储模式
1. 大端模式(big-endian)
数据的高字节存放在内存的低位,低字节存放在内存的高位(比较符合视觉效果);
2. 小端模式(little-endian)
数据的高字节存放在内存的高位,低字节存放在数组的低位。
比如16bit宽的数0x1234在CPU内存中的存放方式(假设从地址0x4000开始存放)为:
Little-endian模式:
内存地址 | 0x4000 | 0x4001 |
存放内容 | 0x34 | 0x12 |
Big-endian模式:
内存地址 | 0x4000 | 0x4001 |
存放内容 | 0x12 | 0x34 |
{
union w
{
int a;
char b;
} c;
c.a = 1;
return (c.b == 1);
}