一大端小端模式
大端模式指的是数据的高位字节存放在内存的低地址端,低位字节存放在内存的高地址端;
小端模式指的是数据的低位字节存放在内存的低地址端,高位字节存放在内存的高地址端;
目前常用计算器(X86)都是小端模式。
举例说明:
16bit的数据0x1234,其高位字节是0x12低位字节是0x34,按照上述描述在CPU寄存器存放如下(假设地址从0x4000开始):
内存地址 | 小端模式 | 大端模式 |
0x4000 | 0x34 | 0x12 |
0x4001 | 0x12 | 0x34 |
内存地址 | 小端模式 | 大端模式 | |
0x4001 | 0x78 | 0x12 | |
0x4002 | 0x56 | 0x34 | |
0x4003 | 0x34 | 0x56 | |
0x4004 | 0x12 | 0x78 |
bool isBIgendian(){
int a=0x1234;
char b=*(char *)&a;//取a的低地址
if(b==0x12)return 1;
else
return 0;
}
bool isSmallendian(){
union Aa{
int a;
char b;
}aa;
aa.a=0x1234;
if(aa.b==0x34)return 1;
else return 0;
}
二内存对齐
什么是内存对齐:
假设同时声明两个变量:char a; short b;
用&取变量a和b的地址会发现,若a的地址是0x0000,那么b的地址将会是0x0002(16位CPU),那么0x0001去哪了?答案就是它确实没被使用。因为CPU每次都是从以2字节(16位CPU)或是4字节(32位CPU)的整数倍的内存地址中读进数据的。如果变量b的地址是0x0001,那么CPU必须从0x0000(上一个short的后8位)读取高8位(后8位按照计算器的小端模式即是高8位)放入b的低8位,然后再从0x0002(下一个short的前8位)读取低8位放入b的高8位最终组成b,故为了获取b的值CPU需要进行两次读操作而如果b的地址是0x0002那么CPU只需读取一次就可以获取b的值了。所以编译器为了优化代码,会尽量把数据放在它的对界上以提高内存命中率.
结构体内存对齐规则
2.1 自然对界
缺省情况下,编译器为结构体的每个 成员按其自然对界(natural alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。自然对界(natural alignment)即默认对齐方式,是指按结构体的成员中size最大的成员对齐。
请牢记以下3条原则:(在没有#pragma pack宏的情况下)
1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍(最大基本类型成员大小或者是结构体的基本数据类型成员或者成员是结构体时内部的基本数据类型)不足的要补齐.编译器会在最末一个成员之后加上填充字节。对于复杂类型(如结构),它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个而不是结构体本身大小。
typedef struct bb
{
int id; //[0]....[3]
double weight; //[8].....[15] 原则1,4不是8的
float height; //[16]..[19],总长要为8的整数倍,补齐[20]...[23] 原则3,20不是8的整数倍故再加上4个
}BB;
typedef struct aa
{
char name[2]; //[0],[1]
int id; //[4]...[7] 原则1 2不是4的整数倍
double score; //[8]....[15]
short grade; //[16],[17]
bb b; //[24]......[47] 原则2 内部最大元素大小8的整数倍地址开始
}AA;
int main()
{
AA a;
cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;
return 0;
}
结果是:
48 24
再举个例子:
struct naturalalign
{
char a; //[0]
short b; //[2]-[3]
char c; //[4]此时按照原则3有5个不是2的整数倍故加上1个 [5]
};
sizeof后应该为6
struct bb{
int id;
double weight;
float height;}BB;//由上面知道size为24
struct aa{
char name[20];//[0]-[19]
int id;//[20]-[23]
double score;//[24]-[31]
bb b;//b为结构体,起始位置要从b成员的子成员大小8整数倍开始[32]-55
}AA;
cout<<sizeof(BB)<<" "<<sizeof(AA)<<endl;
故结果: 24 56
2.2 指定对界,#pragma pack (n)
一般地,可以通过下面的方法来改变缺省的对界条件:
使用伪指令#pragma pack (n),编译器将按照PPB即n个字节对齐,n=1,2,4,8;
使用伪指令#pragma pack (),取消自定义字节对齐方式。
结构体占用的字节数要能被PPB整除
注意:如果#pragma pack (n)中指定的n大于等于结构体中最大成员的size,则其不起作用,结构体仍然按照默认的对齐方式。
#pragma pack(1)
struct bb{
int id;
double weight;
float height;}BB;
struct aa{
char name[2];
int id;
double score;
short grade;
bb b;
}AA;
cout<<sizeof(BB)<<" "<<sizeof(AA)<<endl;
结果 16 32
即对于BB 4+8+4=16对于AA 2+4+8+2+16=32。这相当于没有内存对齐或者说是所有的对齐都按照1的整数倍对齐.
当令#pragma pack (n)n=2:
#pragma pack(2)
struct bb{
int id;[0]-[3]
double weight;[4]-[11]
float height;[12]-[15]
}BB;//size为16
struct aa{
char name[2];//[0]-[1]
int id;//[2]-[5]
double score;//[6]-[13]
short grade;//[14]-[15]
bb b;//[16]-[31]
}AA;//size is 32
cout<<sizeof(BB)<<" "<<sizeof(AA)<<endl;
当令#pragma pack (n)n=4:
#pragma pack(4)
struct bb{
int id;[0]-[3]
double weight;[4]-[11]
float height;[12]-[15]
}BB;//size为16
struct aa{
char name[2];//[0]-[1]
int id;//[4]-[7]
double score;//[8]-[15]
short grade;//[16]-[17]
bb b;//[20]-[35]
}AA;//size is 36
cout<<sizeof(BB)<<" "<<sizeof(AA)<<endl;
当令#pragma pack (n)n=8:
#pragma pack(8)//等于最大基本数据类型大小8故按照自然对界
struct bb{
int id;[0]-[3]
double weight;[8]-[15]
float height;[16]-[19],20不是8的整数倍扩展[20]-[23]
}BB;//size为24
struct aa{
char name[2];//[0]-[1]
int id;//[4]-[7]
double score;//[8]-[15]
short grade;//[16]-[17]
bb b;//[24]-47]
}AA;//size is 48
cout<<sizeof(BB)<<" "<<sizeof(AA)<<endl;
无论是哪种对齐方式注意:
1.每个成员分别按自己的方式对齐,并能最小化长度。
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。