如何使用结构和位域来确保精确的位顺序

how to use struct and bitfields to ensure precise bit order

我已经搜索了很多关于这个主题的内容,希望最终能在这里找到答案。

我想为特定硬件使用一个结构,我想在 C 中实现它;这是我想要的数据包:

typedef struct {
    quint8 startByte        :8;
    quint16 ch1             :11;
    quint16 ch2             :11;
    quint16 ch3             :11;
    quint16 ch4             :11;
    quint16 ch5             :11;
    quint16 ch6             :11;
    quint16 ch7             :11;
    quint16 ch8             :11;
    quint16 ch9             :11;
    quint16 ch10            :11;
    quint16 ch11            :11;
    quint16 ch12            :11;
    quint16 ch13            :11;
    quint16 ch14            :11;
    quint16 ch15            :11;
    quint16 ch16            :11;
    quint8 endByte1         :8;
    quint8 endByte          :8;
}packet;

如果您计算大小,它是 25 个字节。但是当我使用 sizeof(packet) 时,我得到 46。 现在我正在使用 Qt 5.5,我还想在带有 AVR 的 Atmel studio 7 中使用此代码。 顺便说一下,我也使用了 #pragma pack(1)__attribute__((__packed__)) 并得到了 sizeof(packet) 等于 35.

你有 16 个短裤 * 2 个字节 = 32 个字节 + 起始字节 + 两个结束字节 - 如果它被打包,总共 35 个字节。由于您对 11 位字段使用 16 位数据类型,因此您只剩下 5 个字节 - 您无法将 11 位字段放入 5 位,因此这 5 位基本上被浪费了。

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
| field 1 |xxxxx| field 2 |xxxxx| field 3 |xxxxx

如果我没理解错的话,你想实现的大概是这样的:

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
| field  1 | field  2 | field  3 | field  4 |...

我不确定编译器是否可以为你生成这样的布局,但正如评论中已经提到的,你可以手动完成。编译器不这样做是因为它效率低下 - 为了读取一个跨越边界的字段,您必须进行两次读取、掩码、移位并将最终结果组合在一起。

我链接的答案详细解释了位操作,这里唯一不同的是你会跨越边界。

| byte1 | byte2 | byte3 | byte4 |
0123456701234567012345670123456701
| field  1 | field  2 | field  3 |

例如你想读取字段 2 - 它的索引是 1,所以 1 * 11 = 11 - 这是字段的位偏移量。 11 / 8 = 1,余数为 3。这意味着它的值从第二个字节的第 3 位开始,占据其中的 8 - 3 = 5 位,以及下一个字节的 11 - 5 = 6 位。假设字段值为 00100110111.

| byte1 | byte2 | byte3 | byte4 |
0123456701234567012345670123456701
| field  1 | field  2 | field  3 |
        xxx00100110111xx

该值将包含在两个字节 xxx00100110111xx 中,并且可以通过将两个字节移位和组合来重构它,如下所示:

xxx00100          << 8 AND
        110111xx
xxx00100110111xx

然后左移 3 位,去掉前一个字段的前 3 位,然后右移 3 + 2 位,去掉下一个字段的最后 2 位,得到 0000000100110111 简而言之就是该字段的值。

写入字段的过程类似,但是,请确保您没有破坏相邻字段中的位 - 您需要保存并在为目标字段写入字节时正确恢复这些位。