假设我有数据,如下所示:
union
{
struct
{
char flags : 4;
uint16_t : 12;
}
char data[2];
}
我了解如何使该代码在平台上运行而不管字节顺序如何。我要求确保我对如何将其存储在不同字节序上的理解是正确的。
据我了解: 如果我将 uint16 存储在 12 位 uint 中,则两个字节序都会丢弃 4 个最高位。 Big-endian 将剩余的 4 个高位存储在与标志相同的字节中,其余的存储在单独的字节中。 Little-endian 会将 4 个最低位存储在与标志相同的字节中,其余的存储在单独的字节中。
这样对吗?
这取决于编译器和目标平台的 ABI。参见,例如GCC 位域的规则:单元内位域的分配顺序是由 ABI 确定。此外,每个字段都应该声明为
int
或 unsigned int
,而不是 uint16_t
。
如果要控制数据格式,可以使用移位和掩码将数据组装成
uint16_t
。如果您的目标是以明确定义的格式写出数据,则可以按照所需的字节顺序写入 uint16_t
字节,或者将数据组装成 2 个字节并按所需的顺序写入。
除非您找到语言规范文档承诺您想要的内容,或者您的编译器文档做出明确承诺,并且您对大端和小端 CPU 使用相同的编译器,否则不要依赖 C/C++ 编译器来执行类似的操作同样的方式。
Little-endian 会将 4 个最低位存储在与标志相同的字节中
🤔 我熟悉的所有编译器都只组合相邻的位域如果它们的基本存储单元是相同的(尽管是那些可爱的实现定义的细节之一)。因此,在您的示例中,将
char
与 uint16_t
混合会破坏它们的组合,这意味着该结构将使用 3 个字节(对于任一字节顺序)。对两个字段使用相同的基类型可以让你想要你想要的(但我会static_assert(sizeof(...) == 2)
以防万一):
union
{
struct
{
uint16_t flags : 4;
uint16_t value : 12;
}
uint8_t data[2];
}
对于小尾数,位域总是按照存储单元的内存递增顺序添加。
对于大端机器,我遇到过两种可能的位域顺序:
每个位的布局是:
绝对比特流 | 字节 | 以字节为单位 | LE订单 | 是订单#1 | 是订单#2 |
---|---|---|---|---|---|
0 | 0 | 0 | 标志0 | 值8 | 值4 |
1 | 0 | 1 | 标志1 | 值9 | 值5 |
2 | 0 | 2 | 标志2 | 值A | 值6 |
3 | 0 | 3 | 标志3 | 值B | 值7 |
4 | 0 | 4 | 值0 | 标志0 | 值8 |
5 | 0 | 5 | 值1 | 标志1 | 值9 |
6 | 0 | 6 | 值2 | 标志2 | 值A |
7 | 0 | 7 | 值3 | 标志3 | 值B |
8 | 1 | 0 | 值4 | 值0 | 标志0 |
9 | 1 | 1 | 值5 | 值1 | 标志1 |
10 | 1 | 2 | 值6 | 值2 | 标志2 |
11 | 1 | 3 | 值7 | 值3 | 标志3 |
12 | 1 | 4 | 值8 | 值4 | 值0 |
13 | 1 | 5 | 值9 | 值5 | 值1 |
14 | 1 | 6 | 值A | 值6 | 值2 |
15 | 1 | 7 | 值B | 值7 | 值3 |