C语言中带位域的结构体填充后大小确定及重排影响问题
结构体大小计算与成员重排分析
先看问题中的结构体定义:
struct test { unsigned int x; long int y : 33; unsigned int z; };
一、如何确定该结构体的实际大小?
计算结构体大小要结合平台基础类型尺寸、位域存储规则、内存对齐要求这三个核心点,不同编译器和32/64位平台的结果会有差异,具体可以按以下步骤分析:
明确平台基础类型的大小
先确认目标环境下的基本类型尺寸:- 多数64位系统中,
unsigned int为4字节,long int为8字节; - 32位系统中,
unsigned int和long int通常都是4字节,但33位的位域无法塞进单个4字节单元,编译器会自动分配更大的存储单元(比如两个4字节单元)。
- 多数64位系统中,
拆解成员的内存分配与填充
以64位系统为例:x占4字节,接下来要存y——y是基于long int的位域,存储单元为8字节,因此需要对齐到8字节边界,x后面会补上4字节的填充(padding);y的33位放在8字节的存储单元里,剩下的31位空间浪费;z占4字节,放在y的8字节之后,此时总长度为4(x)+4(填充)+8(y)+4(z)=20字节,但结构体整体要对齐到最大基本类型的尺寸(8字节),所以还要补4字节填充,最终大小为24字节。
如果是32位系统:
y的33位需要两个4字节单元(共8字节)来存储;x(4字节)+y(8字节)+z(4字节)=16字节,结构体对齐到4字节(最大基本类型尺寸),最终大小为16字节。
实际验证方法
- 直接用
sizeof运算符计算:写一段简单代码编译运行就能得到结果,示例代码:#include <stdio.h> struct test { unsigned int x; long int y : 33; unsigned int z; }; int main() { printf("结构体大小:%zu\n", sizeof(struct test)); return 0; } - 查看编译器的内存布局:比如GCC可以用
-fdump-struct-layouts选项编译,生成的文件会详细展示每个成员的偏移量和填充字节。
- 直接用
二、结构体成员重排后大小变化的规律
成员重排本质是改变了内存填充的位置和数量,要准确掌握这一情况,记住以下关键规则:
内存对齐的核心原则
- 每个成员的起始地址必须是自身基本类型尺寸的整数倍(比如
unsigned int起始地址要能被4整除,long int要能被8整除); - 结构体整体的大小必须是其中最大基本类型尺寸的整数倍。
- 每个成员的起始地址必须是自身基本类型尺寸的整数倍(比如
重排优化的思路
把占内存大的成员放在前面,小成员放在后面,能大幅减少填充字节。比如将原结构体重排为:struct test_opt { long int y : 33; unsigned int x; unsigned int z; };在64位系统中,这个结构体的大小是16字节:
y占8字节,x紧跟其后(起始地址8,符合4字节对齐),z紧跟x(起始地址12,符合4字节对齐),总长度8+4+4=16字节,刚好满足8字节的整体对齐要求,没有额外填充。位域的特殊处理
- 位域的存储单元基于其声明的基础类型,同类型的位域可能共享一个存储单元(如果空间足够),但普通成员无法和位域共享存储单元——普通成员必须满足自身的对齐要求,会从新的对齐位置开始存储。
编译器对齐选项的影响
编译器提供了强制取消对齐的选项,比如GCC的__attribute__((packed)),用这个修饰结构体后,大小就是所有成员的实际占用字节之和,但会导致未对齐访问,可能降低性能甚至在部分平台出错,示例:struct test_packed { unsigned int x; long int y : 33; unsigned int z; } __attribute__((packed));64位系统下这个结构体的大小是16字节,没有任何填充。
内容的提问来源于stack exchange,提问作者Prashanth Sagar
相关产品推荐
相关产品推荐

