具有不同 Endianness 实现的位字段的分配是特定的

Is assignment of bit field with different Endianness implementation specific

众所周知,我们有两种类型的字节顺序:大端和小端。

假设一个整数需要 4 个字节,所以整数 1 的布局应该是 0x01 0x00 0x00 0x00 对于小端和 0x00 0x00 0x00 0x01 用于大端。

要检查一台机器是小端还是大端,我们可以编写如下代码:

int main()
{
    int a = 1;
    char *p = (char *)&a;
    // *p == 1 means little endian, otherwise, big endian

    return 0;
}

据我了解,*p 分配了第一个八位字节:0x01 用于小端,0x00 用于大端(上面的两个粗体部分),代码就是这样有效。

现在我不太明白位域如何与不同的 Endianness 一起工作。

假设我们有这样一个结构:

typedef struct {
    unsigned char a1 : 1;
    unsigned char a2 : 1;
    unsigned char a6 : 3;
}Bbit;

然后我们做如下作业:

Bbit bit;
bit.a1 = 1;
bit.a2 = 1;

这段代码是特定于实现的吗?我在问 bit.a1bit.a2 的值是否在小端上是 1 而在大端上是 0?或者它们肯定 1 而不管 Endianness 的不同?

C标准甚至不要求表示整数的字节必须是大端顺序或小端顺序(它们可以混合)更不用说位域的顺序了。这些都是实现-defined,这意味着 C 标准没有指定它们,但要求将它们记录在编译器手册或其他文档中。以字节或其他单位为单位的位域顺序不必与对象中的字节顺序相匹配。

假设我们有一个结构:

typedef struct {
    unsigned char a1 : 1;
    unsigned char a2 : 1;
    unsigned char a6 : 3;
}Bbit;

和一个定义:

Bbit byte;

假设 byte 存储在单个字节中并且当前已清零:0000 0000.

byte.a1 = 1;

这会将名为 a1 的位设置为 1。如果a1是第一位,那么byte就变成了1000 0000,但是如果a1是第五位,那么byte就变成了0000 1000 , 如果 a1 是第八位, 那么 byte 就变成了 0000 0001.

byte.a2 = 1;

这会将名为 a2 的位设置为 1。如果 a2 是第二位,那么 byte 已经(很可能)变成了 1100 0000,但是如果 a2 是第六位,那么 byte 已经(很可能)变成0000 1100,如果a2是第7位,那么byte就变成了0000 0011。 (这些只是“可能”,因为不能保证这些位遵循某种合理的顺序。编译器不太可能会特意弄乱这个例子。)

对于存储的值,字节顺序不是一个因素。每次分配仅更改表示指定位字段的位,并且分配的值减少到该位数(如果该值对于该位数来说太大,则具有实现定义的行为)。

对于位域,不仅定义了字节字节序实现,还定义了位字节序。

C standard 第 6.7.2.1p11 节关于结构状态:

An implementation may allocate any addressable storage unit large enough to hold a bit-field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains,whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.

因此编译器可以根据需要自由地对结构中的位域重新排序。作为这方面的一个例子,这里是表示 IP header in /usr/include/netinet/ip.h on Linux:

的结构
struct iphdr
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
    u_int8_t tos;
    u_int16_t tot_len;
    u_int16_t id;
    u_int16_t frag_off;
    u_int8_t ttl;
    u_int8_t protocol;
    u_int16_t check;
    u_int32_t saddr;
    u_int32_t daddr;
    /*The options start here. */
  };

这里可以看到有两个字段是位字段,声明的顺序取决于使用的字节顺序。

所以这意味着如果您通过网络发送原始结构,则不能依赖任何特定的字节(或位)顺序。

根据您的示例添加一些内容以查看表示:

Bbit bit;
bit.a1 = 1;
bit.a2 = 1;
unsigned char *p = (unsigned char *)&bit;
printf("%02x\n", *p);

大端系统可能会打印 a0 而小端系统可能会打印 03。并且假设未使用的位恰好设置为 0。