【问题标题】:Structure with bit-fields size具有位域大小的结构
【发布时间】:2019-06-26 14:06:47
【问题描述】:

我尝试使用具有不同大小位域的结构。使用的总位数为 64。但是,当我检查结构大小时,我得到 11 而不是预期的 8。通过尝试分解结构,我发现差异来自 day 字段。如果我打包每个位以获得 8 位包,则 day 字段在 month 的“结束”和 hour 的“开始”之间打包时间>。我不知道这是否是一个好方法。有人可以解释一下吗?

typedef unsigned char uint8_t;

typedef struct frameHeader_t
    {
        uint8_t encryption       : 2;
        uint8_t frameVersion     : 2;
        uint8_t probeType        : 4;
        uint8_t dataType         : 5;
        uint8_t measurePeriod    : 3;
        uint8_t remontePerdiod   : 4;
        uint8_t nbrMeasure       : 2;
        uint8_t year             : 7;
        uint8_t month            : 4;
        uint8_t day              : 5;
        uint8_t hour             : 5;
        uint8_t minute           : 6;
        uint8_t second           : 6;
        uint8_t randomization    : 5;
        uint8_t status           : 4;

    }FrameHeader;

int main()
{

    FrameHeader my_frameHeader;
    printf("%d\n", sizeof(FrameHeader));
    return 0;
}

【问题讨论】:

  • 您可能需要#pragma pack 或类似的(取决于您的编译器,或者更准确地说,编译器的预处理器)。 year 字段“着陆”在两个字节上,因此很可能它是由编译器填充的。随后,它后面的一些字段也“降落”在两个字节上,因此应用了更多填充。
  • 问题是这样的:“...一个预期的 8”。没有预期的位域大小。它们不能可靠地用于内存映射。
  • 在纯 C 中,不能保证结构的内存布局。
  • 在 C 中,打包位域的传统方法是将它们打包到大小与声明的整数类型相同的整数中。因此,在您的示例中,它们将被打包成 8 位整数。如果一个位域不合适,它会跳到一个新的整数。我相信从那时起包装要求已经放宽了,但这仍然是一种常见的策略。所以在你的情况下,包装是 (1) 2, 2, 4 (2) 5, 3 (3) 4, 2 (4) 7 (5) 4 (6) 5 (7) 5 (9) 6 (9) 6 (10) 5 (11) 4. 所以你最终得到 11 个 8 位整数。如果您想改进打包,请切换到更大的整数类型,例如uint32_t.

标签: c struct bit-fields


【解决方案1】:

如果你通过pahole 工具运行它,你应该得到一个解释:

struct frameHeader_t {
    uint8_t                    encryption:2;         /*     0: 6  1 */
    uint8_t                    frameVersion:2;       /*     0: 4  1 */
    uint8_t                    probeType:4;          /*     0: 0  1 */
    uint8_t                    dataType:5;           /*     1: 3  1 */
    uint8_t                    measurePeriod:3;      /*     1: 0  1 */
    uint8_t                    remontePerdiod:4;     /*     2: 4  1 */
    uint8_t                    nbrMeasure:2;         /*     2: 2  1 */

    /* XXX 2 bits hole, try to pack */

    uint8_t                    year:7;               /*     3: 1  1 */

    /* XXX 1 bit hole, try to pack */

    uint8_t                    month:4;              /*     4: 4  1 */

    /* XXX 4 bits hole, try to pack */

    uint8_t                    day:5;                /*     5: 3  1 */

    /* XXX 3 bits hole, try to pack */

    uint8_t                    hour:5;               /*     6: 3  1 */

    /* XXX 3 bits hole, try to pack */

    uint8_t                    minute:6;             /*     7: 2  1 */

    /* XXX 2 bits hole, try to pack */

    uint8_t                    second:6;             /*     8: 2  1 */

    /* XXX 2 bits hole, try to pack */

    uint8_t                    randomization:5;      /*     9: 3  1 */

    /* XXX 3 bits hole, try to pack */

    uint8_t                    status:4;             /*    10: 4  1 */

    /* size: 11, cachelines: 1, members: 15 */
    /* bit holes: 8, sum bit holes: 20 bits */
    /* bit_padding: 4 bits */
    /* last cacheline: 11 bytes */
};

您使用 uint8_t 作为基本类型,因此字段被填充为 8 位组。

您应该能够完全消除填充,通过使用unsigned long long/uint_least64_t(至少 64 位大)作为位域的基本类型,比使用 __attribute((packed)) 更便携,但从技术上讲,非不保证支持位域的 int/non-unsigned-int 基本类型,但您可以在稍微重新组织位域后使用unsigned(C 标准保证至少 16 位),例如:

typedef struct frameHeader_t
{
    //16
    unsigned year             : 7;
    unsigned randomization    : 5;
    unsigned month            : 4;

    //16
    unsigned second           : 6;
    unsigned minute           : 6;
    unsigned status           : 4;

    //16
    unsigned hour             : 5;
    unsigned dataType         : 5;
    unsigned probeType        : 4;
    unsigned encryption       : 2;

    //16
    unsigned day              : 5;
    unsigned remontePerdiod   : 4;
    unsigned measurePeriod    : 3;
    unsigned nbrMeasure       : 2;
    unsigned frameVersion     : 2;

}FrameHeader; 
//should be an unpadded 8 bytes as long as `unsigned` is 16,
//32, or 64 bits wide (I don't know of a platform where it isn't)

(不能保证填充或缺少填充,但除非必要,否则我从未见过实现插入它。)

【讨论】:

  • 您的解决方案的问题是它不保留字段顺序,因此当接收到的数据喜欢自己的顺序时它就无用了。
  • @P__J__ 我确实提到使用 unsigned long long/uint_least64_t 应该可以修复它而无需重新排序。
  • @P__J__ 对于 IO,我从 uin8_t 数组显式位操作应该是最便携的方法。
【解决方案2】:

为了避免包装魔法,我通常使用大小相同的固定大小的无符号类型

typedef struct frameHeader_t
    {
        uint64_t encryption       : 2;
        uint64_t frameVersion     : 2;
        uint64_t probeType        : 4;
        uint64_t dataType         : 5;
        uint64_t measurePeriod    : 3;
        uint64_t remontePerdiod   : 4;
        uint64_t nbrMeasure       : 2;
        uint64_t year             : 7;
        uint64_t month            : 4;
        uint64_t day              : 5;
        uint64_t hour             : 5;
        uint64_t minute           : 6;
        uint64_t second           : 6;
        uint64_t randomization    : 5;
        uint64_t status           : 4;

    }FrameHeader;

https://godbolt.org/z/BX2QsC

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-30
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    • 1970-01-01
    • 2016-11-21
    相关资源
    最近更新 更多