结构体、位域、栈的字节对齐
为什么要字节对齐
可移植性:不是所有硬件都能从任意地址开始访问数据,如 MIPS 遇到未对齐内存直接报错
效率:CPU 每次读取内存时是一块一块读取的,如果目标数据跨越了两个块,CPU 要把两个块都读进来,去掉无关字节,然后将两个块中的目标数据拼接起来,大大降低了 CPU 的效率
结构体/联合体中的字节对齐
结构体内的第一个成员的偏移量为0,此后每个成员的偏移量为 min(#pargma pack(),成员自身长度)的倍数
对于联合体,每个成员的偏移地址都是0
如果结构体中嵌套结构体/联合体,那么嵌套的结构体/联合体的偏移量必须是它最大成员的字节数的整数倍
最后,结构体/联合体总的字节数必须是最宽基本类型成员所占字节数的整数倍(包括嵌套的结构体中的基本成员)
struct outer {
int a;
struct {
double d;
char c;
int i;
}in;
};
//sizeof(struct outer) == 24 sizeof(struct inner) == 16
struct outer {
int a;
union inner{
double d;
char c;
int i;
}s;
};
//sizeof(struct outer) == 16 sizeof(union inner) == 8
union outer {
int a;
union inner{
double d;
char c;
int i;
}s;
};
//sizeof(union outer) == 8 sizeof(union inner) == 8
位域的压缩与字节对齐
首先简单介绍一下位域(bit filed、位段)
位域用于声明将数据以位的形式紧凑储存,并允许直接对位域变量进行操作
位域类型变量不可取址
优点:①节省空间;②可以方便将一个整数值的部分位作为整体访问、操作
缺点:位域的压缩与对齐是平台、机器相关的,难以移植
位域的压缩:
- 如果相邻位域字段的位域类型相同,各个字段只占定义时的位长度
- 如果一个位域字段的长度压缩时会跨越两个位域类型存储单元,则对齐到第二个单元存储
- 如果位域字段间有非位域字段,则这两个位域字段不进行压缩
位域的对齐:如果相邻的位域字段类型不同,则按照结构体对齐规则对位域类型进行对齐
位域就像,按位域类型正常分配空间,压缩性地使用部分空间
struct test{
char c1:1;
char c2:2;
char c3:7;
int i:3;
char c4:2;
};
//sizeof(struct test) == 12, c3从第二个字节开始, i从第五个字节开始, c4从第九个字节开始
调用栈的字节对齐
主要是为了支持 SSE(Streaming SIMD Extensions)浮点运算扩展指令集与16字节的 XMM 浮点寄存器
GCC 栈帧的边界默认以16字节对齐,栈帧内按正常规则对齐
编译器对字节对齐的支持
对于 VC 来说,有 #pargma pack(n) 预处理命令,如 #pargma pack(8),指定按照8字节对齐
#pargma pack(push) 、#pargma pack(pop) 保存当前字节对齐状态、弹出栈顶字节对齐状态
#pragma pack (),取消自定义字节对齐方式
对于GCC,除了 #pargma pack(n) 预处理命令外
还可以使用 __attribute__ 机制来指定字节对齐数,如在描述GDT表项的数据结构中,
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
unsigned limit_high: 4;
unsigned flags: 4;
uint8_t base_high;
} __attribute__((packed));
attribute 中 packed 表明 struct / union 中的成员紧紧地打包在一起,不进行字节对齐(亲测 DEVC gcc,还是进行了对齐)
可以使用 aligned (alignment) 属性来指定字节对齐数
如 __attribute__((aligned(4))) 四字节对齐,__attribute__((aligned)) 默认对齐方式
此外,attribute 属性的效果与链接器也有关,attribute 属性指定的字节对齐数,需要链接器也支持
2019/12/22