字节对齐
1.问题的提出
谈到字节对其人们都感觉很容易,其实不然,如果你把握不了真正的内部原则,怎么做怎么错,下面具体介绍一下字节对其的原则:
写出一个struct或者union,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢?讲讲字节对齐吧.
2.为什么要字节对齐
体系结构的对齐和不对齐,是在时间和空间上的一个权衡。字节对齐是指数据在内存地址必须是数据长度的整数倍。WORD为2个字节,DWORD为4个字节。字节对齐其实是一种牺牲空间换取时间的做法,这样有利于提高CPU的执行效率。
3.字节对齐的三个原则
在没有#pragma pack()宏的情况下,方可使用下面说法:
【原则1】数据成员对齐规则
结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。
【原则2】结构体作为成员
如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(structa里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
【原则3】收尾工作
结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
4.举例说明数据对齐
【一般难度挑战】
typedef struct bb
{
int id; //[0]....[3]
double weight; //[8].....[15] 原则1
float height; //[16]..[19],总长要为8的整数倍,补齐[20]...[23] 原则3
}BB;
typedef struct aa
{
char name[2]; //[0],[1]
int id; //[4]...[7] 原则1
double score; //[8]....[15]
short grade; //[16],[17]
BB b; //[24]......[47] 原则2,原则3(不足要补齐)
}AA;
int main()
{
AA a;
cout<<sizeof(a)<<""<<sizeof(BB)<<endl;
return 0;
}
结果是
48 24
【高难度挑战难度挑战】
有如下结构定义:请给出具体结果:
#include<stdio.h>
typedef struct m_part_info_t
{
unsigned char active; //a[0]
unsigned char start_head; //a[1]
unsigned short start_sector:6;
unsigned short start_cyl:10; //a[2]与a[3]被start_sector与start_cyl共同占据
unsigned int size; //a[4]-a[7]
unsigned long VolumeLable[11]; //a[8]-a[51]
unsigned char FileSysType[8]; //a[52]-a[59]
}part_inf_t;
typedef struct tag_msgmode
{
char msg_id;
union
{
struct
{
charname[32];
int id;
}model;
struct
{
charyear;
charmonth;
charday;
charhour;
charminute;
charsecond;
}model2;
char padding[64];
};
}msg_mode_t;
int main(int args,char * argv[])
{
printf("%d\n",sizeof(part_inf_t));
printf("%d\n",sizeof(msg_mode_t));
return 0;
}
【输出结果】:60 68
你是否为这样的输出结果而感到困惑呢?其实只要记住上边三条原则,这种题不攻自破,不过另外还要提醒大家,共用体存储数据的原则是共同占用一段存储空间,所以所需空间为长度最长的字段所占的空间。
具体空间图如下:
m_part_info_t的占用空间结果图如下所示:
如果将结构体中6改为7结果为64字节,存放原则如下:
typedef struct m_part_info_t
{
unsigned char active; //a[0]
unsigned char start_head; //a[1]
unsigned short start_sector:7;
unsigned short start_cyl:10; //a[2]与a[3]被start_sector与start_cyl共同占据
unsigned int size; //a[4]-a[7]
unsigned long VolumeLable[11]; //a[8]-a[51]
unsigned char FileSysType[8]; //a[52]-a[59]
}part_inf_t;
对于msg_mode_t所占字节大小如下图:
此时占据存储空间的基本内容就搞定了,可以说掌握了这些基本没什么大问题。
5. #pragma pack(n).
#pragma pack(n)对于n的取值范围为1, 2, 4, 8, and 16.
在代码前加一句#pragma pack(1),你会很高兴的发现,在程序输出过程中就会按照期望的进行输出,本省占用多少内存就是多少。如下:
typedef struct bb
{
int id; //[0]....[3]
double weight; //[8].....[15] 原则1
float height; //[16]..[19],总长要为8的整数倍,补齐[20]...[23] 原则3
}BB;
bb是4+8+4=16
typedef struct aa
{
char name[2]; //[0],[1]
int id; //[4]...[7] 原则1
double score; //[8]....[15]
short grade; //[16],[17]
BB b; //[24]......[47] 原则2,原则3(不足要补齐)
}AA;
aa是2+4+8+2+16=32;
这不是理想中的没有内存对齐的世界吗.没错,#pragma pack(1),告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则.
那#pragma pack(2)的结果又是多少呢?就是所用对齐按照2的整数倍对齐。依次这样下去就OK了。