一、理解
1.1 编译器默认对齐方式
多少位的编译器就默认多少位对齐,譬如:32位的编译器,默认4字节对齐。
求证方法很简单,自己写一个结构体,存放几个不同类型的元素,打印出结构体大小即可。
1.2 对齐分析(基于4字节对齐)
(1)结构体起始地址必须在4字节对齐处,对齐后的大小为4的倍数;
(2)每个元素根据自身的对齐规则,对齐存放,参考:三、应用->3.1默认对齐方式;
二、方法
2.1 #pragma pack()与#pragma pack(n)
(1)解释
#pragma pack( ):更改为编译器默认对齐方式;
#pragma pack(n):更改为n字节对齐。
(2)用法
用于改变一个区间内结构体的对齐方式;
在区间头部使用#pragma pack(n),末尾使用#pragma pack();
我也试过不在区间末尾加#pragma pack();,程序也可以运行,但保险起见,还是加上。
2.2 __attribute__((packed))与__attribute__((aligned(n)))
(1)解释
__attribute__((packed)):更改为1字节对齐;
__attribute__((aligned(n))):更改为n字节对齐。
注意:下划线和括号都是两个,一个都不能少!
(2)用法
两个都是更改单个结构体的对齐方式;
放在结构体定义末尾处即可。
三、应用
3.1 默认对齐方式
(1)特例
#include <stdio.h>
struct MyStruct1 //1字节 4字节
{
char a ;// 1 1
char b;// 1 1
short c;// 2 2
int d ;// 4 4
};
int main(void)
{
printf("Mystruct1 = %d.\n",sizeof(struct MyStruct1));
return 0;
}
输出结果:
MyStruct1 = 8;
受变量类型本身的对齐规则的影响,1字节、4字节对齐,结构体占用空间大小相同
(2)一般情况
#include <stdio.h>
struct MyStruct1 //1字节 4字节
{
char a ;// 1 4(1+3)
int d ;// 4 4
char b;// 1 1
short c;// 2 3(2+1)
};
int main(void)
{
printf("Mystruct1 = %d.\n",sizeof(struct MyStruct1));
return 0;
}
输出结果:
MyStruct1 = 12;
3.2 #pragma pack()
参考默认对齐方式,这里不再赘述。
3.3 #pragma pack(n)
#include <stdio.h>
#pragma pack(8);
//__attribute__(aligned(8)) __attribute__((packed))
struct MyStruct1 //1字节 4字节 8字节
{
char c_a ;// 1 4(1+3) 4(1+3)
int b ; // 4 4 4
short s_c;// 2 4(2+2) 8(2+6)
};
struct MyStruct2
{
char c_d;// 1 4(1+3) 4(1+3)
int i_e;// 4 4 4
struct MyStruct1 S1;// 7 12 16
};
#pragma pack();
int main(void)
{
printf("MyStruct1 = %d.\n",sizeof(struct MyStruct1));
printf("MyStruct2 = %d.\n",sizeof(struct MyStruct2));
// printf("MyStruct11 = %d.\n",sizeof(struct MyStruct11));
// printf("MyStruct21 = %d.\n",sizeof(struct MyStruct21));
return 0;
}
输出结果:
MyStruct1 = 12.
MyStruct2 = 20.
思考一下,MyStruct2为什么是28呢,double的对齐规则是怎样呢?
3.4 __attribute__((packed))
#include <stdio.h>
struct MyStruct1 //1字节 4字节
{
char c_a ;// 1 4(1+3)
int i_b ;// 4 4
short s_c;// 2 4(2+2)
};
struct MyStruct2
{
char c_d;// 1 4(1+3)
int i_e;// 4 4
struct MyStruct1 S1;// 7 12
};
struct MyStruct11
{
char c_a1 ;// 1
int i_b1 ;// 4
short s_c1;// 2
}__attribute__((packed));
struct MyStruct21
{
char c_d1;// 1
int i_e1;// 4
struct MyStruct11 S11;// 7
}__attribute__((packed));
int main(void)
{
printf("MyStruct1 = %d.\n",sizeof(struct MyStruct1));
printf("MyStruct2 = %d.\n",sizeof(struct MyStruct2));
printf("MyStruct11 = %d.\n",sizeof(struct MyStruct11));
printf("MyStruct21 = %d.\n",sizeof(struct MyStruct21));
return 0;
}
输出结果:
MyStruct1 = 12.
MyStruct2 = 20.
MyStruct11 = 7.
MyStruct21 = 12.
3.5 __attribute__((aligned(n)))
使用__attribute__((aligned(n)))测试1、2、8、16字节对齐
#include <stdio.h>
// #pragma pack(8);
//__attribute__((aligned(8))) __attribute__((packed))
struct MyStruct1 //1字节 4字节 8字节 16字节
{
char c_a ;// 1 4(1+3)
int i_b ;// 4 4
short s_c;// 2 4(2+2)
};
struct MyStruct2
{
char c_d;// 1 4(1+3)
int i_e;// 4 4
struct MyStruct1 S1;// 7 12
double d_f;// 8 8
};
struct MyStruct11
{
char c_a1 ;// 1 4(1+3) 4(1+3) 4(1+3)
int i_b1 ;// 4 4 4 4(1+3)
short s_c1;// 2 4(2+2) 8(2+6) 8(2+6)
}__attribute__((aligned(1)));
struct MyStruct21
{
char c_d1;// 1 4(1+3) 4(1+3) 4(1+3)
int i_e1;// 4 4 4(1+3) 4(1+3)
struct MyStruct11 S11;// 7 12 16 16
double d_f1; // 8 8 8 16(8+8)
}__attribute__((aligned(1)));
// #pragma pack();
int main(void)
{
printf("MyStruct1 = %d.\n",sizeof(struct MyStruct1));
printf("MyStruct2 = %d.\n",sizeof(struct MyStruct2));
printf("MyStruct11 = %d.\n",sizeof(struct MyStruct11));
printf("MyStruct21 = %d.\n",sizeof(struct MyStruct21));
return 0;
}
输出结果:
MyStruct1 = 12.
MyStruct2 = 28.
MyStruct11 = 12.
MyStruct21 = 28.
更改为1字节并没有用,这里打印结构体MyStruct11、MyStruct21各元素的地址:
MyStruct1 = 12.
MyStruct2 = 28.
MyStruct11.c_a1 = 0x804a020.
MyStruct11.i_b1 = 0x804a024.
MyStruct11.s_c1 = 0x804a028.
MyStruct11 = 12.
MyStruct21.c_d1 = 0x804a02c.
MyStruct21.i_e1 = 0x804a030.
MyStruct21.S11 = 0x804a034.
MyStruct21.d_f1 = 0x804a040.
MyStruct21 = 28.
下面测试一下2字节对齐:
输出结果:
MyStruct1 = 12.
MyStruct2 = 28.
MyStruct11.c_a1 = 0x804a020.
MyStruct11.i_b1 = 0x804a024.
MyStruct11.s_c1 = 0x804a028.
MyStruct11 = 12.
MyStruct21.c_d1 = 0x804a02c.
MyStruct21.i_e1 = 0x804a030.
MyStruct21.S11 = 0x804a034.
MyStruct21.d_f1 = 0x804a040.
MyStruct21 = 28.
下面测试一下8字节对齐:
输出结果:
MyStruct1 = 12.
MyStruct2 = 28.
MyStruct11.c_a1 = 0x804a040.
MyStruct11.i_b1 = 0x804a044.
MyStruct11.s_c1 = 0x804a048.
MyStruct11 = 16.
MyStruct21.c_d1 = 0x804a060.
MyStruct21.i_e1 = 0x804a064.
MyStruct21.S11 = 0x804a068.
MyStruct21.d_f1 = 0x804a078.
MyStruct21 = 32.
下面测试一下16字节对齐:
输出结果:
MyStruct1 = 12.
MyStruct2 = 28.
MyStruct11.c_a1 = 0x804a040.
MyStruct11.i_b1 = 0x804a044.
MyStruct11.s_c1 = 0x804a048.
MyStruct11 = 16.
MyStruct21.c_d1 = 0x804a060.
MyStruct21.i_e1 = 0x804a064.
MyStruct21.S11 = 0x804a070.
MyStruct21.d_f1 = 0x804a080.
MyStruct21 = 48.
分析:使用 __attribute__((aligned(n)))更改结构体对齐数时:
(1)无法更改1、2字节(≤4)的对齐数,改完后还是默认的4字节对齐;
(2)更改为8、16字节(>4)的对齐数,完全按照字节对齐分布,以16字节对齐为例:
MyStruct11.c_a1 为char型,剩下字节不够存放i_b1,实际为(1+3)字节;
MyStruct11.i_b1 为int型,占用4字节;
MyStruct11.s_c1 为short型,占用2字节,后面没有元素了,实际占用为(2+6)字节。
所以16字节对齐时,MyStruct11大小为:4+4+8 = 16字节。
MyStruct21.c_d1为char型,占用1字节,剩下字节不够存放i_e1,实际为(1+3)字节;
MyStruct21.i_e1为int型,占用4字节,剩下字节不够存放S11,实际为(4+8)字节;
MyStruct21.S11占用16字节,刚好占满;
MyStruct21.d_f1 为double类型,占用8字节,后面没有元素,实际占用为(8+8)字节。
所以16字节对齐时,MyStruct21占用大小为:4+12+16+16 = 48字节。
分析就到这里,就不再深入了,前面留了#pragma pack(8)对齐方式,就不再赘述了。
我也百度了很多资料,都没有详细讲是何种原因造成两种对齐方式的异常现象。于是自己测试了一下,也可能和编译器优化,不同的系统有关系。能力有限,没有深入研究对齐方式异常的原因,这篇博客主要提供一个解决对齐问题的思路,并对对齐方式做个详细记录。如有大佬提供思路,愿闻其详。