【嵌入式底层知识修炼】结构体成员对齐之#pragma pack(n)和__attribute__((aligned (n)))的含义和区别


不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。

————CSDN赵4老师


  暂且约定C语言中每一个基础类型大小如下:

类型 sizeof(x)
char 1
short 2
int 4
long 4
float 4
double 8

  结构体成员对齐有2个规则,分别是自然对齐自定义对齐,其中自定义对齐分为两个修饰方式:#pragma pack(n)__attribute__((aligned (n))),这两种修饰方式的含义是不同的,需要加以理解和区别。

01 - 提出问题

  现在有一个结构体成员对齐问题,看代码

struct example1{
    char a;
    int b;
    char c;
}__attribute__((aligned(2)));

#pragma pack(2)
struct example2{
    char a;
    int b;
    char c;
};
#pragma pack()

  要求输出:

printf("%d-%d",sizeof(struct example1),sizeof(struct example2));

  example1example2的成员是一样的,另外同样要求结构体成员按2字节对齐,如果你得到的答案是两者一致,那么就错了,最后结果会是多少?答案在下文解答。

02 - 对齐规则

  上文提及到,对齐规则分为自然自定义,自然对齐规则意思是默认对齐规则,即没有任何自定义对齐规则约束的时候编译器将默认采用自然对齐规则。

2.1 - 自然对齐规则

  自然对齐:按照结构体成员中size最大的成员对齐

  • 如果结构体A内还有结构体B,则相当于把结构体B的成员放到结构A中,再进行size成员对齐,这个过程可以是反复迭代的。

2.2 - 自定义对齐之#pragma pack(n)

  #pragma pack(n):结构体的成员相对于第一个成员地址的偏移量的对齐方式,需要是n的倍数

  • 1、n必须是大于0的2的次方值
  • 2、默认对齐规则:是自然对齐规则
  • 3、如果指定的n大于结构体中最大成员的size,则按照默认对齐规则
  • 4、#pragma pack()表示接下来的内容取消对齐优化,按照自然对齐规则进行对齐
### 一般用法 ###

#pragma pack(4)		//按4字节对齐
struct example1{
    char a;
    int b;
    char c;
};
#pragma pack()		//结束对齐

2.3 - 自定义对齐之__attribute__((aligned (n)))

  __attribute__((aligned (n))):指定结构体类型的变量分配地址空间时的地址对齐方式,该结构体类型的变量在分配地址空间时,其存放的地址一定按照n字节对齐,并且其占用的空间也是n的整数倍

  • 1、n必须是大于0的2的次方值
  • 2、默认对齐规则:先按自然对齐规则计算总大小,然后取一个2的次方值,使得该值大于等于总大小
  • 3、如果指定的n大于结构体中最大成员的size,则按照默认对齐规则
  • 4、如果指定的n小于结构体中某个成员的size,则按照自然对齐规则
  • 5、修饰结构体后,该结构体在后续的任何地方都将保持修饰后计算得到的值进行字节对齐
  • 6、__attribute__((packed))表示取消对齐优化,先按照自然对齐规则,最末尾的位置不再对齐
### 一般用法 ###

struct example1{
    char a;
    int b;
    char c;
}__attribute__((aligned(4)));	//按4字节对齐

2.4 - 两种自定义对齐方式的区别

  #pragma pack(n):针对的是结构体内每一个成员相对第一个成员的偏移地址对齐
  __attribute__((aligned (n))):针对的是整个结构体在整个内存地址空间分配时的对齐。
【嵌入式底层知识修炼】结构体成员对齐之#pragma pack(n)和__attribute__((aligned (n)))的含义和区别

03 - 解答问题

  按照所约定的类型大小,上述问题输出应该是12-8

struct example1{
    char a;
    int b;
    char c;
}__attribute__((aligned(2)));

  强调的是整个结构体在内存的地址,由于n=2小于结构体中的sizeof(b) = 4,所以对齐规则变成自然对齐规则,根据自然对齐规则,按照结构体中size最大的成员(也就是4字节)进行对齐,所以结果为4(char)+4(int)+4(char) = 12

#pragma pack(2)
struct example2{
    char a;
    int b;
    char c;
};
#pragma pack()

  强调的是成员之间的偏移地址,所以按照2字节对齐就是2(char)+4(int)+2(char) = 8

04 - 问题扩展

  想加深#pragma pack(n)__attribute__((aligned (n)))的了解,可以完成下面的题目,计算每一个结构体的大小:

#pragma pack()
struct example1	//求sizeof(struct  example1)
{
    short a;
    long b;
};
-------------------------
#pragma pack(2)
struct example1	//求sizeof(struct  example1)
{
    char a;
    int b;
    char c;
};
-------------------------
#pragma pack(4)
struct example1	//求sizeof(struct  example1)
{
    char a;
    int b;
    char c;
};
-------------------------
#pragma pack(8)
struct example1	//求sizeof(struct  example1)
{
    char a;
    int b;
    char c;
};
-------------------------
#pragma pack(8)
struct example1	//求sizeof(struct  example1)和sizeof(struct  example2)
{
    short a;
    long b;
};
struct example2
{
    char c;
    example1 struct1;
    short e;
};

struct example1	//求sizeof(struct  example1)
{
    char a;
    short b;
    char c;
}__attribute__((packed));
-------------------------
struct example1	//求sizeof(struct  example1)
{
    char a;
    long b;
    short c;
}__attribute__((packed));
-------------------------
struct example1	//求sizeof(struct  example1)
{
    char a;
    long b;
    short c;
}__attribute__((aligned (2)));
-------------------------
struct example1	//求sizeof(struct  example1)
{
    char a;
    long b;
    short c;
}__attribute__((aligned (4)));
-------------------------
struct example1	//求sizeof(struct  example1)
{
    char a;
    long b;
    short c;
}__attribute__((aligned (8)));
-------------------------
struct example1	//求sizeof(struct  example1) 和 sizeof(struct  example2)
{
    short a;
    long b;
}__attribute__((aligned (8)));
struct example2
{
    char c;
    example1 struct1;
    short e;
};

05 - 总结

  • #pragma pack(n)强调的是成员之间的偏移地址
  • __attribute__((aligned (n)))强调的是整个结构体在内存的地址