在阅读gnu软件c源代码时,经常会遇到字节对齐相关操作,比如uboot命令相关的代码中,会遇到__attribute__((aligned(n)))
扩展关键字,#pragma pack(n)
预处理指令,修饰变量或者类型后,会产生怎样的影响呢?
1. #pragma pack(n)
#pragma pack(n)
是一条预处理指令,告诉编译器结构体或类内部的成员变量相对于第一个变量的地址的偏移量的对齐方式,缺省情况下,编译器按照自然边界对齐,当变量所需的自然对齐边界比n大时,按照n对齐,否则按照自然边界对齐;使用#pragma pack()
取消指定对齐,恢复缺省对齐。
下面案例中,st结构体2字节对齐,结构体大小为4字节。
#pragma pack(8)
struct st
{
short f[2];
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
下面案例中,st结构体2字节对齐,结构体大小为6字节。
#pragma pack(2)
struct st
{
char c;
int i;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
总结为下面2个结论:
1)作用对象:#pragma pack(n)
指定的是结构体成员的对齐边界;
2)对齐边界:alignment = min(n, max(member alignment))
2. __attribute__((aligned(n)))
2.1 修饰变量
int more_aligned_int __attribute__((aligned(8)));
int类型自然对齐边界是4字节对齐。指定对齐后,整型变量more_aligned_int将在内存中8字节边界对齐。
2.2 修饰结构体类型
2.2.1 语法格式
__attribute__(((aligned(n)))
修饰结构体,可以放在结构体struct关键字之后,或者类型定义最后一个花括号之后,分号之前。但是更推荐前一种,因为从逻辑上讲,花括号结束意味着类型定义的结束。(https://gcc.gnu.org/onlinedocs/gcc-11.2.0/gcc/Common-Type-Attributes.html#Common-Type-Attributes)
struct st
{
short f[2];
}__attribute__((aligned(8)));
2.2.2 对结构体变量对齐的影响
__attribute__((aligned(n)))
告诉编译器一个结构体或者类或者联合或者一个类型的变量(对象)分配地址空间时的地址对齐方式。
也就是说,如果将__attribute__((aligned(n)))
作用于一个类型,那么该类型的变量在分配地址空间时,其存放的地址一定按照n字节对齐(n必须是2的幂次方)。并且其占用的空间,即大小,也是n的整数倍,以保证在申请连续存储空间的时候,每一个元素的地址也是按照n字节对齐。
特殊的,当结构体成员中自然对齐边界最大值m大于n时,相应的对齐边界则为m,结构体大小也为m的整数倍。
struct st
{
short f[2];
}__attribute__((aligned(8)));
上面案例中,结构体大小为8字节,起始地址8字节对齐;
struct st
{
char c;
int i;
}__attribute__((aligned(2)));
上面案例中,结构体起始地址4字节对齐,结构体大小为4的整数倍,为8字节,注意不是6字节,因为结构体成员i为int类型,自然对齐边界为4,大于指定对齐边界2。
最后总结为下面4个结论:
1)关于作用对象:该类型定义的变量,而不是该类型的成员。成员按照自然边界对齐
2)关于对齐边界:alignment = max(n, max(member alignment))
3)关于起始地址:start_addr align to alignment
4)关于占用空间:var_size=N*alignment