x86-64小端系统下结构体位域内存布局的疑问
位域内存布局疑问解答
结构体定义
typedef struct football_game { unsigned short num_players : 4; // 无符号短整型位域,占4位 unsigned int num_managers : 1; // 无符号整型位域,占1位 unsigned short num_cameras : 8; // 无符号短整型位域,占8位 long int num_vip : 16; // 长整型位域,占16位 unsigned int num_screens : 4; // 无符号整型位域,占4位 } statistics; // 赋值操作 statistics week_a; week_a.num_players = 0xf, week_a.num_managers = 1; week_a.num_cameras = 0xff, week_a.num_vip = 0xfff; week_a.num_screens = 0xf;
环境ABI信息
系统为小端序,可执行文件属性如下:
file a.out a.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=xxx for GNU/Linux 3.2.0, with debug_info, not stripped
疑问1:未使用__attribute__((packed))时,num_screens为何被分配到独立字节?
这是x86-64 System V ABI对位域和结构体对齐的规则导致的,核心原因是位域的存储单元基类型不同+结构体成员对齐要求:
- 位域存储单元规则:每个位域的存储单元大小等于其声明的基类型大小(比如
unsigned short位域用2字节单元,unsigned int用4字节单元),且不同基类型的位域不能共用同一个存储单元,即使前一个单元有剩余空间。 - 结构体成员对齐规则:每个成员必须对齐到其基类型的对齐边界(
unsigned short对齐2字节,unsigned int对齐4字节,long int对齐8字节)。
具体内存布局拆解(以结构体起始地址0xXXXXXX80为例):
num_players(unsigned short,4位):占用0x80-0x81的低4位(小端序下是0x80字节的低4位),剩余12位无法被其他不同基类型的位域使用。num_managers(unsigned int,1位):基类型要求4字节对齐,当前位置到0x81未对齐到4字节,因此填充2字节到0x83,从0x84-0x87的最低1位存储该位域,剩余31位无法被unsigned short类型的num_cameras复用。num_cameras(unsigned short,8位):基类型要求2字节对齐,当前位置0x84已满足对齐,占用0x84-0x85的低8位(0x84字节全部),剩余8位无法被后续不同基类型的位域使用。num_vip(long int,16位):基类型要求8字节对齐,当前位置到0x85未对齐到8字节,填充3字节到0x87,从0x88-0x8F的低16位(0x88-0x89字节)存储该位域,剩余48位无法被unsigned int类型的num_screens复用。num_screens(unsigned int,4位):基类型要求4字节对齐,当前位置到0x89未对齐到4字节,填充2字节到0x8B,最终从0x8C开始的4字节单元存储该位域,也就是你在GDB中看到的0x7fffffffdf8c。
疑问2:使用__attribute__((packed))时的具体打包方式
__attribute__((packed))会取消结构体成员的强制对齐要求(对齐边界变为1字节),但位域的核心规则(存储单元为基类型大小、不同基类型位域不共用存储单元)仍然生效,布局会尽可能紧凑:
num_players(unsigned short,4位):占用起始2字节的低4位,剩余12位闲置。num_managers(unsigned int,1位):无需对齐到4字节,紧接前一个单元,占用0x02-0x05的最低1位,剩余31位闲置。num_cameras(unsigned short,8位):紧接前一个单元,占用0x06-0x07的低8位,剩余8位闲置。num_vip(long int,16位):紧接前一个单元,占用0x08-0x0F的低16位,剩余48位闲置。num_screens(unsigned int,4位):紧接前一个单元,占用0x10-0x13的最低4位。
最终结构体大小为20字节(0x00-0x13),所有成员紧密排列,无额外对齐填充,但不同基类型的位域仍各自占用独立的基类型大小存储单元。
疑问3:位域内存布局的ABI规则与通用规范
x86-64平台遵循System V AMD64 ABI,其中对位域的核心规则如下:
- 存储单元:位域的存储单元大小等于其声明的基类型大小,位域不能跨越自身存储单元的边界(即一个位域必须完全包含在单个存储单元内)。
- 对齐要求:未加
packed时,每个位域的存储单元必须对齐到其基类型的对齐边界;加packed后,对齐边界变为1字节,仅保证位域不跨自身存储单元。 - 类型隔离:不同基类型的位域不能共用同一个存储单元,即使前一个存储单元有剩余空间。
- 位序规则:小端序下,位域的位从存储单元的最低位(LSB)开始分配;大端序则从最高位(MSB)开始。
- 零长度位域:若声明长度为0的位域(如
unsigned int : 0;),会强制开启一个新的存储单元,且对齐到当前基类型的边界。 - 结构体整体对齐:未加
packed时,结构体的整体对齐等于其最大成员基类型的对齐要求;加packed后,整体对齐为1字节。
注意:位域属于C语言的实现定义特性,不同编译器(如GCC、Clang)或平台(如ARM、x86)的规则可能存在细微差异,上述规则仅适用于x86-64的System V ABI环境。
内容的提问来源于stack exchange,提问作者Anton
相关产品推荐
相关产品推荐

