【问题标题】:How do bit fields actually work? and in what case bit padding happens?位域实际上是如何工作的?在什么情况下会发生位填充?
【发布时间】:2021-09-09 07:06:24
【问题描述】:

我最近被介绍到位域。我有一个以下工会。

 typedef struct
  {
            uint16_t        var1:16;         
            uint32_t       var2:28;         
            uint8_t        var3:8;       
            uint8_t        var4:8;     
            uint8_t        var5:8;   
            uint8_t        var6:8;  
            bool        var7:1;  
            bool         var8:1;          
            bool         var9:1;    
            bool         var10:1;  
            bool         var11:1;    
            uint8_t         var12:1;    
            uint8_t        var13:7;        
            uint8_t        var14:7;   
            uint32_t       var15:18;           
            uint16_t       var16:10;         
            uint8_t        var17:4;         
  } packet_bit_map;
typedef union
{

packet_bit_map packetsArrived ;
  uint8_t packetRaw[16];
} packetDecode;

这个想法是复制来自外部设备的数据流,它是 16 字节的单个字节值,然后使用位域结构来访问特定信息。 但是在调试后我无法做到这一点,我发现联合 packetDecode 的大小最终为 20 而不是 16 ,因为 packetsArrived 持有 20bytes 。为什么会这样?以及如何避免这种填充?

编辑:我知道一种解决方案是使用属性填充,但遗憾的是我不能在我的项目中使用内联函数。

【问题讨论】:

  • TL;DR:“位域实际上是如何工作的?”不良。 “在什么情况下会发生位填充?”没有人知道,因为它没有标准化。 “我最近被介绍到位领域”由谁?通过一些值得信赖的 C 源或随机的“友好”人?
  • 我不久前写了一个关于why bitfields should not be used for serialization的答案。我认为大部分适用于您的情况。
  • 即使你把它打包成 16 个字节,也不能保证结构会匹配源。 - 打包的顺序也是实现定义的。为此目的使用位域不是一个好主意。使用位域的理由很少,也没有办法使其可移植。

标签: c embedded deserialization union bit-fields


【解决方案1】:

如果您想打包:

  1. 将字段分组为完全适合的类型。
  2. 请勿使用bool 作为位域的类型。
  3. 打包结构(依赖于实现)
 typedef struct __attribute__((packed))
  {
            uint16_t        var1:16;         
            uint64_t        var2:28;         
            uint64_t        var3:8;       
            uint64_t        var4:8;     
            uint64_t        var5:8;   
            uint64_t        var6:8;  
            uint64_t        var7:1;  
            uint64_t        var8:1;          
            uint64_t        var9:1;    
            uint64_t        var10:1;  
            uint16_t        var11:1;    
            uint16_t        var12:1;    
            uint16_t        var13:7;        
            uint16_t        var14:7;   
            uint32_t        var15:18;           
            uint32_t        var16:10;         
            uint32_t        var17:4;         
  } packet_bit_map;

https://godbolt.org/z/Pz3YzEE76

【讨论】:

  • 实际上 C 标准明确表示位域可以保存 bool,尽管它没有提及此类 bool 成员将有多大或它们将如何打包是无益的。它说“_Bool 位域具有 _Bool 的语义”,这基本上是一个没有 s**t 的 Sherlock 语句。我们仍然不知道 bool 是 1 位还是 8 位。然后“结构中紧跟在另一个位字段之后的位字段应被打包到同一单元的相邻位中”。理论上应该打包布尔值。但这不是强制性的,C 标准允许编译器进行完全不合理的实现。
  • @Lundin 如果你有 1 位无符号位域 bool 有什么意义?
  • C 标准没有提到 1 位位域。它只是无益地说“一个位域的类型应该是 _Bool、signed int、unsigned int 或其他一些实现定义的类型的限定或非限定版本。”选择的类型意味着编译器必须保留“可寻址存储单元”,这是一些仅与位字段一起使用的模糊的大词,基本上是实际分配的内存量。
【解决方案2】:

考虑在 packet_bit_map 的结构声明中使用#pragma pack#pragma pack的用法和效果在这个帖子的回答中解释的很好:#pragma pack effect

【讨论】:

  • 请参阅@0__________ 答案中的第 3 点。您链接到的问题适用于 Microsoft 的编译器,并且这个问题被标记为 [embedded]。 #pragma 指令是总是实现定义的,无法识别的指令会被简单地忽略。答案也没有明确提到位域。位域的打包和排序在任何情况下都是由实现定义的,因此单独打包可能无法实现所需的行为。
  • 好的,我明白了。谢谢你的澄清。我的想法实际上并不适用于有问题的特定编译器。
【解决方案3】:
6.7.2.1 结构和联合说明符
...
5     位域的类型应为 _Boolsigned intunsigned intunsigned int、或其他一些实现定义的类型。这是 实现定义是否允许原子类型。
...
11     实现可以分配任何大到足以容纳位域的可寻址存储单元。如果有足够的空间,一个位域紧跟在另一个位域之后 结构应打包到同一单元的相邻位中。如果剩余空间不足, 不适合的位域是否放入下一个单元或与相邻单元重叠是 实现定义。一个单元内位域的分配顺序(高位到 低阶或低阶到高阶)是实现定义的。对齐方式 未指定可寻址存储单元。
C 2011 Online Draft

在英语中,这就是说,将位域映射到存储,以及位域是否彼此直接相邻或是否有缝隙。查看您的代码,var1 占用 16 位(两个完整的 8 位字节),var2 占用 28 位(三个半 8 位字节),var3 占用 8 位。一个合理的映射会为var1 分配一个2 字节的存储单元,为var2 分配一个4 字节的存储单元,以及一个额外的存储单元来保存全部或部分var3。所以问题是var3是否跨越两个字节,或者它是否有自己的字节:

+----- var1 -----+---------- var2 ------------+- var3 -+
|                |                            |        |        span
+----------------+----------------------------+----+--------+
|                |                                 |        |
+----------------+----------------------------+----+--------+
|                |                            |    |        |   no span                               
+----- var1 -----+---------- var2 ------------+    +- var3 -+

由实现决定使用哪种方法。鉴于 var3var6 都是 8 位,我希望实现分配另一个 4 字节存储单元并将所有四个成员映射到它,在 var2 和 @ 之间留下 4 位间隙987654336@。所以是的,生成的struct 类型占用的字节数超过了所建议的位总和。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-14
    • 2011-11-23
    • 2016-05-19
    • 1970-01-01
    • 2015-11-14
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多