前言
大家好,我是小昭,因为在不同硬件平台数据传输时,遇到关于字节对齐的问题,索性就做了总结,以下是我对字节对齐的理解和小结,如有疑问请联系我。
目录
结构体变量占用空间不同
为什么要字节对齐?(内存对齐)
字节对齐的规则
typedef struct data_type1{
char a[2];
short b;
int c;
} Data_type1;
typedef struct data_type2{
short a;
int b;
char c[2];
} Data_type2;
先看看上面两个结构体,如果有定义成对应的变量,它俩占内存空间是多少 ?使用sizeof()占用多少空间,都是8个字节大小,答案不是。
Data_type1和Data_type2结构体里的成员变量类型相同,两者的所占的空间应该是相同的,但是事实却不是如此。
经过实践编写代码输出的结果,却是这样的。
/*ubuntu gcc环境 */
#include
#include
#include
#define debug_printf(value) printf(#value " ---==> %d\n", value)
#define debug_struct_member_offset(struct, member) (((char *)(&(((struct *)0)->member))) - ((char *)0))
typedef struct data_type1{
char a[2];
short b;
int c;
} Data_type1;
typedef struct data_type2{
short a;
int b;
char c[2];
} Data_type2;
int main(int argc, char** argv){
int a,b,c;
a = debug_struct_member_offset(Data_type1,a);
b = debug_struct_member_offset(Data_type1,b);
c = debug_struct_member_offset(Data_type1,c);
debug_printf(sizeof(Data_type1));
debug_printf(a);
debug_printf(b);
debug_printf(c);
a = debug_struct_member_offset(Data_type2,a);
b = debug_struct_member_offset(Data_type2,b);
c = debug_struct_member_offset(Data_type2,c);
debug_printf(sizeof(Data_type2));
debug_printf(a);
debug_printf(b);
debug_printf(c);
}
Data_type1和Data_type2的类型变量占用的大小分别是8和12.
因为编译器对数据进行了优化,做了字节对齐操作,可以增加内存的使用率和数据高效得传输。
什么是字节对齐?
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,计算机并非逐个字节读取,而是以2、4、8的倍数字节块读取内存,它们会要求这些数据的首地址的值是某个数是k(通常是4或8)的倍数 ,这就是所谓的内存对齐。大白话就是,各种数据类型都要一定的规则进行排列,而不是一个接一个的排放,这就是对齐。
1、减少cpu访问变量的次数,cpu可以更快的读取数据
2、合理的使用字节对齐,可以节省内存的大小。
3、减少 cpu 访问数据的出错性(有些 cpu 必须内存对齐,否则指针访问会出错),不同硬件平台进行数据通信,数据对齐可能会不一致,需要加入伪指令进行操作,防止灾难性性bug。
规则一:结构体变量的首地址能够被字节对齐的大小整除(gcc 缺省字节对齐大小是4)。
规则二:结构体的每个成员相对首地址的偏移是成员类型大小的整数倍。(成员自身对齐)
规则三:结构体变量的总大小是结构体里最大的成员的整数倍。(结构体本身对齐)
计算结构体变量的字节大小,以上面的例子说明
Data_type1结构体变量类型,大概这样存放的。
计算字节大小可以不关心规则一,可以被忽略。看规则二,第二个成员b的偏移是2是short大小(2)的整数倍,第三个成员c的偏移量是4是int的大小的整数倍。看规则三,总的大小是8字节,是int大小的整数倍,c成员后面不需要填内容。
看规则二,第二个成员b的偏移是2不是int大小(4)的整数倍,成员a后面补2个字节才能被4整除,第三个成员c的偏移量是8是char的大小的整数倍。看规则三,总的大小是10字节,不是是int大小(4)的整数倍,成员c补上内容(2),直到能被4整除,所以总大小是12个字节。
在设计数据结构的时候,可以看出合理调整成员的排序,可以节省内存使用。但是需要在空间上和可读性进行权衡。
思考,结构体类型,被实体化后,占用的字节大小:
typedef struct data_type3{
char a;
char b;
char c;
} Data_type3;
typedef struct data_type4{
char a;
char b[2];
//char c;
} Data_type4;
答案都是3.
有什么办法可以对字节对齐进行干预?
1、使用#prama park(n)伪指令,会对前面规则二和规则三进行干预,大白话,成员本身字节对齐和结构体本身对齐大小不能大于n,只能小,如果能理解这这句话,问题就不大了,直接看例子吧。
#include
#include
#include
#define debug_printf(value) printf(#value " ---==> %d\n", value)
#define struct_member_offset(struct, member) (((char *)(&(((struct *)0)->member))) - ((char *)0))
#pragma pack(4) //4字节对齐开始 对齐大小只能比4小,不能大
typedef struct
{
uint32_t b;
/*double 8字节对齐,这里正常是要补4字节内容,
但是编译器这里规定最多只能4个字节对齐,
如果是short就按2个字节对齐,只能小不拿大*/
double f;
} test_t;
#pragma pack()//字节对齐结束
int main()
{
int b = struct_member_offset(test_t, b);
int f = struct_member_offset(test_t, f);
debug_printf(sizeof(test_t));
debug_printf(b);
debug_printf(f);
return 0;
}
注释#pragma pack(4) 占用空间是16.
2、使用__attribute((aligned(n))),操作结构体时,这里可以看成是对规则三进行干预,对结构体本身对齐。
typedef struct
{
uint8_t a; // 偏移地址
uint16_t b; // 偏移地址
}__attribute((packed)) test_t1; //按实际空间
typedef struct
{
uint8_t a; // 偏移地址
uint16_t b; // 前补1 偏移地址
}__attribute((aligned(4))) test_t2;
typedef struct
{
uint8_t a; // 偏移地址
uint16_t b; //前补1 偏移地址
}__attribute((aligned(8))) test_t3;
typedef struct
{
uint8_t a; // 偏移地址
uint16_t b; //前补1 偏移地址
}__attribute((aligned(16))) test_t4;
//#pragma pack()
int main()
{
debug_printf(sizeof(test_t1));
debug_printf(sizeof(test_t2));
debug_printf(sizeof(test_t3));
debug_printf(sizeof(test_t4));
return 0;
}
运行结果
留个坑:
typedef struct data_type1{
char a[2];
short b;
int c;
} Data_type1;
typedef struct data_type2{
short a;
Data_type1 b; //Tips: 这部分按里面最大成员大小对齐 所以要补. 至于补多少……
char c[2];
} Data_type2;
sizeof(Data_type2)是多少?
答案:16
以上是结果,都是经过我测试验证成功,如有疑问,请麻烦联系我。我是小昭!