如何使用结构和位域来确保精确的位顺序
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
该值将包含在两个字节 xxx00100
和 110111xx
中,并且可以通过将两个字节移位和组合来重构它,如下所示:
xxx00100 << 8 AND
110111xx
xxx00100110111xx
然后左移 3 位,去掉前一个字段的前 3 位,然后右移 3 + 2 位,去掉下一个字段的最后 2 位,得到 0000000100110111
简而言之就是该字段的值。
写入字段的过程类似,但是,请确保您没有破坏相邻字段中的位 - 您需要保存并在为目标字段写入字节时正确恢复这些位。
我已经搜索了很多关于这个主题的内容,希望最终能在这里找到答案。
我想为特定硬件使用一个结构,我想在 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
该值将包含在两个字节 xxx00100
和 110111xx
中,并且可以通过将两个字节移位和组合来重构它,如下所示:
xxx00100 << 8 AND
110111xx
xxx00100110111xx
然后左移 3 位,去掉前一个字段的前 3 位,然后右移 3 + 2 位,去掉下一个字段的最后 2 位,得到 0000000100110111
简而言之就是该字段的值。
写入字段的过程类似,但是,请确保您没有破坏相邻字段中的位 - 您需要保存并在为目标字段写入字节时正确恢复这些位。