欢迎光临
我们一直在努力

结构体:位域

目录

前言

位域基础定义与语法

位域是否会丢失数据?

位域核心区分两件事

位域丢失数据的直观体现

容易混淆的误区

位域遵循内存对齐吗?

什么时候会新开存储单元

误区纠正

位域与数据类型

两条硬性规则

位域的定性理解

位域内存分配、存储单元与 padding

0 宽度匿名位域

位域常见限制与注意事项

小结


前言

    前篇:学习总结与结构体内存对齐-CSDN博客

    对内存对齐进一步的AI辅助学习,挺详细的,我能想到的疑惑都提出来了。

位域基础定义与语法

前置基础:结构体内存对齐、对齐粒度、padding 填充、sizeof计算

普通结构体成员,最小占用单位为1 字节(8 bit)。 ** 位域(bit‑field)是 C 语言结构体特有的语法,允许结构体成员以二进制 bit(比特)** 为单位分配内存,不需要占用完整字节。

硬件寄存器、网络协议报文中大量状态标志,仅需要少量比特即可表达。使用位域可以压缩存储,减少内存开销。

基础语法:

C

数据类型 成员名 : 比特数量;

示例代码:

C

struct DeviceFlag {
unsigned int power : 1;
unsigned int speed : 3;
unsigned int alarm : 2;
};

  • 冒号:后面数字,代表该成员占用的 bit 数量。
  • ⚠️硬性约束:位域指定比特数不能超过基础类型总比特。unsigned int占 32bit,位域最大只能写 32。
  • ⚠️移植风险:C 标准没有规定 bit 存放顺序(高位优先 / 低位优先),内存排布由编译器实现决定,不可直接跨平台移植。

表格

属性项详细说明
核心用途 以 bit 粒度存储标志位,压缩存储空间
语法模板 数据类型 成员名 : bit个数;
边界限制 bit 数量 ≤ 基础类型总比特数
跨平台特性 🔴不可直接跨编译器移植


问题

  • 使用位域的核心目的是什么?
  • 基础类型是unsigned int时,位域冒号后最大允许填写多少?
  • 位域结构体能否不加修改直接拿到其他编译器平台运行?
  • 1.压缩所用的内存

    2.32

    3.不能

    那这样不会丢失数据吗?

    你的答案全部正确✅

  • 压缩所用的内存
  • 32
  • 不能
  • 是否还有疑惑?先针对你提出的疑问:那这样不会丢失数据吗?

    位域是否会丢失数据?

    位域本身不会主动丢失数据,丢失数据来自两种错误使用场景:

  • 赋值超过位域能够容纳的 bit 范围
  • c

    struct DeviceFlag {
    unsigned int speed : 3; //仅3bit,最大能保存数值:7
    };
    struct DeviceFlag f;
    f.speed = 10; //10二进制1010,超过3bit

    这里就会发生截断,高位被直接丢弃,出现数据丢失。不是位域本身缺陷,是赋值越界。

  • 跨不同编译器 / 平台解析同一份二进制数据 不同编译器 bit 排布顺序不同。A 编译器把 speed 放在 bit0‑bit2;B 编译器放在 bit5‑bit7。把内存直接拷贝过去解析,解读出来的值错乱,看起来像 “丢数据”,本质是布局不一致,并不是存储的时候丢了。
  • ✅只要在同一个编译器同一个平台内部读写:正常赋值、数值不越界,位域不会丢失数据。

    像位域这样假如int是32bit 改完变成了1bit,这样数据不少了吗?

    先理清你的疑惑:

    原来完整unsigned int是 32bit,现在位域写成 power:1,只使用 1bit,剩下 31bit 去哪里了?数据会不会丢失?

    位域核心区分两件事

  • ✅变量本身能存多大数值(位域的有效位数) unsigned int power :1; 这个成员只分配 1 个 bit。1bit 无符号,只能存:0 或 1。 你如果给它赋值 2、3、100,超出 1bit 表达范围 → 高位截断,数据丢失。
  • 这是赋值越界造成丢失,不是把原来 32bit 的数据硬塞到 1bit 里面。

  • ✅结构体内部的存储单元并没有丢掉剩下的 bit
  • c

    struct DeviceFlag {
    unsigned int power : 1;
    unsigned int speed : 3;
    unsigned int alarm : 2;
    };

    基础类型是unsigned int,存储单元依旧是完整 32bit。

    • power 占 1bit,speed 占 3bit,alarm 占 2bit;
    • 这个 32bit 单元里面,剩下 32‑1‑3‑2 =26 bit,属于未使用的 padding 空闲比特。
    • 这 26bit 还在内存里,只是不给这几个成员使用,不是把内存删了。
    • sizeof(struct DeviceFlag)依然等于 4 字节(32bit),并没有把 int 砍成 1 字节。

    位域丢失数据的直观体现

    c

    struct DeviceFlag f;
    f.power = 1; //ok,1bit可以存
    f.power = 2; //❌越界,二进制 10,只保留低1bit → f.power结果变成0,数据丢失!

    不是把一个 32 位整数 “压缩破坏”,而是:这个成员只允许你使用其中 N 个 bit,剩下的 bit 闲置;如果你塞超过 N 位的数据,就会丢。

    容易混淆的误区

    ❌误区:unsigned int power:1 → 把 int 变成 1 字节 / 1bit 大小的变量。 ✅事实:整个存储单元还是 32bit,该成员只占用其中 1bit 做读写,其余比特空闲 padding。结构体整体大小仍然 4 字节。


    再看表格对比

    表格

    代码该成员可用 bit存储单元总大小结构体 sizeof
    unsigned int power; 32 bit 32 bit 4
    unsigned int power :1; 1 bit 仍然 32 bit 4

    为什么结构体大小变成了4字节?不遵循内存对齐了吗?

    位域遵循内存对齐吗?

    它完全遵循内存对齐。

    c

    struct DeviceFlag {
    unsigned int power : 1;
    unsigned int speed : 3;
    unsigned int alarm : 2;
    };

    sizeof结果 = 4 字节。

  • 位域有一个概念:存储单元 位域写的基础类型是unsigned int,所以存储单元就是unsigned int:4 字节(32bit)。 编译器拿一整块完整的 32bit 空间,来存放这几个位域成员。 power (1)+speed (3)+alarm (2) 一共只用掉 6bit,剩下 26bit 作为 padding 空闲位留在这个存储单元内部,不会新开别的单元。

  • 对齐规则在这里怎么生效?

    • 存储单元本身(unsigned int)对齐要求:按 4 字节对齐。
    • 这一整个 32bit 存储单元,直接作为结构体的一个 “单元成员”。
    • 所有位域全部塞进这同一个存储单元,没有产生第二个单元。 结构体整体大小就等于这个存储单元大小:4 字节。

    不是 “把几个 bit 打包成 1 字节”,而是:直接分配完整的unsigned int大小的存储块,在块内部抠出一部分 bit 给成员使用,剩余 bit 在块内 padding。

    什么时候会新开存储单元

    c

    struct Test{
    unsigned int a : 30;
    unsigned int b : 5;
    };

    存储单元 32bit。 a 占用 30bit,本单元仅剩 2bit。b 需要 5bit,放不下。 于是开辟第二个 unsigned int 存储单元(又 4 字节)。 结构体大小 = 4 + 4 = 8 字节。 同时结构体整体对齐依旧遵从unsigned int4 字节对齐。

    误区纠正

    ❌误区 1:位域用了多少 bit,结构体大小就按 bit 求和换算字节。

    位域是以完整存储单元为粒度申请内存,单元内部才做 bit 级划分。

    ❌误区 2:位域不遵守结构体内存对齐。

    ✅位域的存储单元本身严格遵守对应类型的对齐,padding 分为两种:

  • 单元内 padding:同一个存储单元里面没用上的 bit(上面例子的 26bit)
  • 单元间 padding:多个存储单元之间,为满足对齐产生的字节填充
  • 表格

    结构体占用 bit 总和存储单元数量sizeof对齐依据
    DeviceFlag 1+3+2=6 bit 1 个 unsigned int 4 unsigned int (4 字节)
    Test 30+5=35 bit 2 个 unsigned int 8 unsigned int (4 字节)

    再梳理一遍 DeviceFlag 完整流程

  • 位域基础类型unsigned int,存储单元大小 4 字节,对齐 4 字节。
  • power、speed、alarm 全部可以塞进这一个 32bit 单元。
  • 单元内部消耗 6bit,剩余 26bit 单元内 padding 闲置。
  • 结构体只有 1 个存储单元,整体大小 4 字节,满足 4 字节对齐。
  • 所以位域只能容纳一种数据类型吗?

    位域与数据类型

    一个结构体的位域,可以混合不同基础类型,不是只能用同一种类型。 但是:类型一变,存储单元就会重新开启新单元,这是关键规则。

    c

    struct MixBit{
    unsigned char x : 2; //基础类型 unsigned char →存储单元:1字节(8bit)
    unsigned int y : 10; //基础类型 unsigned int →存储单元:4字节(32bit)
    };

    • x是unsigned char,开辟 **1 字节(8bit)** 的存储单元;x 只用掉 2bit,单元内剩余 6bit padding。
    • 接下来位域的基础类型切换成unsigned int。只要基础类型发生改变,直接结束上一个存储单元,新建对应类型的存储单元。
    • y 放入全新的 4 字节存储单元。

    所以 sizeof(struct MixBit) = 1 + 4 = 5 字节。 同时结构体整体对齐取所有存储单元最大对齐值:unsigned int对齐 4 字节,所以尾部补 padding,最终 sizeof 为 8 字节。


    两条硬性规则

  • 同一个存储单元,只能存放相同基础类型的位域成员 连续声明、类型相同的位域,才会挤在同一个存储单元里面。一旦类型改变,立刻关闭旧存储单元,分配新存储单元。
  • c

    struct S1{
    unsigned int a : 1;
    unsigned int b : 2; //类型不变,a,b共用同一个unsigned int存储单元
    };

    struct S2{
    unsigned int a :1;
    unsigned char b :2; //类型切换!a在旧单元,b开启全新char单元
    };

  • 普通结构体成员和位域共存:普通成员同样触发单元结束
  • c

    struct S3{
    unsigned int a : 1;
    int val; //普通int成员,直接结束前面位域的存储单元
    unsigned int b : 2; //b开启全新unsigned int存储单元
    };

    表格

    代码片段行为
    连续同类型位域 复用同一个存储单元,内部分配 bit
    位域类型发生变化 终止当前存储单元,创建新存储单元
    遇到普通结构体成员 终止前面位域存储单元,正常走对齐

    ⚠️实践建议:写位域尽量全部用同一种基础类型。混用 char /int 会频繁新建存储单元,位域节省内存的优势直接消失。

    问题

  • 结构体里面的位域,是否强制要求全部使用同一种基础数据类型?
  • 连续的位域,什么情况下会开启一个全新的存储单元?
  • 结构体中位域后面写一个普通 int 成员,会对前面的位域存储单元产生什么影响?
  • 1.不会

    2.不够/数据类型转化

    3.结束存储单元 正常对齐

    那我可不可以理解为:在大范围下遵循内存对齐,而“位域”是将一个数据类型的格子划开给其他同种数据以压缩内存?

    位域的定性理解

    你的理解大体方向是对的,我帮你精准打磨这句话,修正细节:

    ✅修正后的理解: 在整体层面,结构体严格遵循内存对齐规则。位域是把同一个基础类型对应的完整存储单元格子,在内部切割成很多小 bit,分配给连续同类型的位域成员,以此实现内存压缩。 这个格子本身大小不变、对齐不变,只是格子内部的 bit 拆分使用;一旦空间不够、或者类型改变,就新开一整个新的格子。

    拆解你的原话,区分哪里对、哪里要注意

    “位域是将一个数据类型的格子划开给其他同种数据以压缩内存”

  • ✔️格子就是存储单元:比如unsigned int,格子就是 4 字节 32bit 整块,对齐依旧是 4 字节,不会把这个 4 字节的格子拆碎和别的格子混在一起。
  • ✔️只有连续、同种基础类型的位域,才会在同一个格子内部瓜分 bit。
  • ❗注意:不是 “给其他同种数据”,是给本结构体内部的其他位域成员,普通结构体成员不能跑到这个 bit 格子里面。普通成员一出现,直接关闭当前 bit 格子。
  • ✔️压缩只发生在格子内部。格子本身大小不会缩小。哪怕只用 6bit,这个unsigned int格子依旧占 4 字节,不会变成 1 字节。压缩节省的是格子内部空闲 bit,而不是减少存储单元的总字节。
  • 举个例子对比:

    c

    //不使用位域,三个独立unsigned int,一共12字节
    struct Normal{
    unsigned int power;
    unsigned int speed;
    unsigned int alarm;
    };

    //使用位域,全部塞到同一个unsigned int存储单元,一共4字节
    struct Bit{
    unsigned int power :1;
    unsigned int speed :3;
    unsigned int alarm :2;
    };

    这里的压缩,就是把本该 3 个独立 4 字节的格子,合并成1 个 4 字节格子,在内部分配 bit,这就是位域压缩的本质。


    位域内存分配、存储单元与 padding

    前置基础:位域基础语法、普通结构体对齐、padding、sizeof

    位域不会无限制连续堆放 bit。 每一组位域,依托声明的基础类型,生成一块完整大小的内存,这块内存叫做存储单元。 例:基础类型unsigned int,存储单元就是 4 字节 (32bit)。

    两条核心逻辑:

  • 如果当前存储单元剩余的 bit,足够放下下一个同基础类型的位域成员,就直接复用这个单元剩下的比特。空闲的 bit 保留在单元内部,作为单元内 padding。
  • 如果剩余 bit 不足以存放下一个成员,直接舍弃本单元剩余 bit,开辟一块全新的同类型存储单元。
  • 示例代码:

    c

    struct BitExp {
    unsigned int a : 16;
    unsigned int b : 17;
    };

    • 存储单元大小 32bit;a占用 16bit,单元还剩余 16bit。
    • b需要 17bit,16bit 空间不够,不能塞进当前单元。
    • 开启第二个全新unsigned int存储单元存放 b。
    • 结构体一共 2 个存储单元,sizeof(struct BitExp) = 8字节。

    ⚠️重要提醒:不能把所有位域 bit 相加 ÷8 来算结构体大小,必须看一共开辟多少个完整存储单元。

    表格

    项目说明
    存储单元大小 等于位域的基础类型大小,unsigned int→4 字节
    空间充足行为 复用当前存储单元剩余 bit,内部产生 padding
    空间不足行为 放弃剩余 bit,新建存储单元
    sizeof 计算 统计全部完整存储单元,再执行结构体整体对齐

    问题

  • 位域的存储单元大小由什么决定?
  • 当前存储单元剩余 bit 放不下下一个同类型位域成员,编译器会做什么?
  • 可不可以直接把所有位域的 bit 总和除以 8 得到结构体大小?
  • 1.声明的基础类型

    2.再开辟另一个存储单元

    3.不行

    全部回答正确✅

  • 声明的基础类型
  • 再开辟另一个存储单元
  • 不行

  • 0 宽度匿名位域

    前置:位域存储单元、padding、sizeof

    unsigned int : 0; 是匿名 0 宽度位域,没有成员名,不占用任何比特,也不能读写数据。 唯一作用:强制关闭当前存储单元,后面的位域必须使用全新的存储单元。

    c

    struct ZeroBit{
    unsigned int a:16;
    unsigned int :0; //强制结束当前单元
    unsigned int b:8;
    };

    • a:16 使用第一个 32bit 单元,还剩 16bit 空闲。
    • :0 直接废弃剩余 16bit,关闭本单元。
    • b:8 不会复用剩余空间,启用第二个存储单元。
    • 最终 sizeof(struct ZeroBit) = 8。

    去掉unsigned int :0;,a 与 b 会挤在同一个单元,sizeof 结果为 4。

    使用场景:硬件寄存器描述,强制让后面位域对齐到存储单元边界。

    表格

    项目说明
    书写 基础类型 : 0;,无变量名
    内存 0bit,无法读写
    效果 强制终止当前存储单元

    问题

  • 0 宽度位域有没有变量名,可以读写访问吗?
  • 0 宽度位域的核心功能是什么?
  • 如果没有 0 宽度位域,剩余 bit 充足,同类型位域会如何?
  • 1.不存在 不能

    2.结束当前存储单元

    3.存储到当前存储单元

    全部回答正确✅

  • 不存在,不能
  • 结束当前存储单元
  • 存储到当前存储单元

  • 位域常见限制与注意事项

    前置:位域存储单元、0 宽度位域

  • 不能对位域成员取地址 &
  • c

    struct Test {
    unsigned int x :4;
    };
    struct Test s;
    &s.x; //编译报错!

    位域成员存于一个 int 内部的若干 bit,没有独立内存地址,指针无法定位单个 bit。

  • 位域不能做数组 不允许 unsigned int arr[5] :2;,C 语法不支持位域数组。

  • 赋值不能超过位域可表达范围 x:3最大只能存 7;赋值大于 7 会发生高位截断,数据出错。

  • 二进制布局不可跨编译器移植 bit 是从高位开始分配还是低位,C 标准没有强制规定。 只在同一编译器内部使用;不要把位域结构体直接当二进制网络报文、磁盘存储。

  • 表格

    条目说明
    取地址& 位域成员禁止取地址
    数组 不支持位域数组
    赋值 超出 bit 范围发生截断
    可移植性 二进制布局编译器相关,跨环境慎用

    问题

  • 能不能对位域成员使用取地址运算符 &?为什么?
  • C 语言是否支持位域数组?
  • 直接把位域结构体当作二进制数据跨编译器存储,存在什么风险?
  • 1.不能 位域成员以bit单位存储,没有独立地址

    2.不支持

    3.出现乱码

    回答全部正确✅

  • 不能;位域按 bit 存放,没有独立内存地址,指针无法定位单个 bit。
  • 不支持。
  • 不同编译器 bit 排布规则不一样,二进制解析错乱,出现乱码、数据错误。

  • 小结

  • 位域依托存储单元(基础类型完整大小),单元内部切割 bit 实现内存压缩,整体依旧遵守结构体内存对齐。
  • 同类型连续位域优先复用存储单元;空间不足、类型切换、普通成员、0 宽度位域,都会触发新建存储单元。
  • 类型 :0; 匿名 0 宽度位域,强制结束当前存储单元,用于硬件寄存器布局控制。
  • 限制:不能&取地址、不能数组、赋值防越界截断、二进制布局不可跨编译器移植。
  • 赞(0)
    未经允许不得转载:171主机测评 » 结构体:位域
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址