0xAA55 发表于 2018-11-7 16:05:14

【C】位域的相邻顺序

位域是一种应该被避开使用的东西,它破坏你的代码的可移植性,降低可读性,影响优化效果,并且增加维护难度。尤其是对于现今视内存如粪土的时代,能不用就不用。
然而如果我非得去使用呢?比如读取一个SD卡的CSD寄存器(Card-Specific Data),它里面就有很多指定位存储的信息:

(图上代码来源于SdFat库的实现:https://github.com/greiman/SdFat-beta/blob/master/SdFat/src/SdCard/SdInfo.h)
对于这种情况,存在一个问题:大小端的不同,会导致位域的位置不同。比如reserved1和csd_ver,谁在前谁在后,光看上述代码片段,是不明确的。

事实上,对于位域的前后顺序问题,C99的定义如下(C99 standard 6.7.2.1/10 - "Structure and union specifiers"):An implementation may allocate any addressable storage unit large enough to hold a bitfield. 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.也就是“如果当前单元还有足够的剩余空间,下一个位域会紧随上一个位域,如果剩余空间不够,这个位域要么被放到下一个单元,要么跨越当前这个单元到下一个单元,取决于实现定义。一个单元里的位域的排列顺序是根据具体实现来定义的。而对于每个可寻址单元的对齐,则是未指定的。”

那么“下一个”和“上一个”到底是哪个方向呢?

而GCC则在它的手册页面“Using the GNU Compiler Collection (GCC): Structures unions enumerations and bit-fields implementation”说:“Determined by ABI”

在GCC的邮件通讯列表里有提示说:Bit-fields are always assigned to the first available bit, possibly constrained by other factors, such as alignment. That means that they start at the low order bit for little-endian, and the high order bit for big-endian. This is the "right" way to do things. It is very unusual for a compiler to do this differently.翻译过来就是“位域永远被放到从第一个可以使用的位开始的位串。可能受到其他因素的限制,例如对齐。这意味着它在小端平台从最低bit开始分配,而从大端平台则从最高bit开始分配。这是最‘正确’的做法。对于编译器而言,用不同的方式做这件事是很不寻常的。”

请看下例代码:#include <stdio.h>

typedef struct tagT
{
        int a:4;
        int b:4;
        int c:8;
        int d:16;
}T;

int main()
{
        char data[]={0x12,0x34,0x56,0x78};
        T *t = (T*)data;
        printf("a =0x%x\n" ,t->a);
        printf("b =0x%x\n" ,t->b);
        printf("c =0x%x\n" ,t->c);
        printf("d =0x%x\n" ,t->d);

        return 0;
}在大小端不同的平台上打印出来的结果是不一样的:// - 大端 :mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
// - 小端 : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856可以看出:在小端平台上,顺序靠前的位域变量它的位置更靠右。而对于大端平台,顺序靠前的位域变量则更靠左一些。

我们再来举一个例子:union
{
        unsigned short value;
        unsigned char byte;
        struct
        {
                unsigned short a : 1;
                unsigned short b : 2;
                unsigned short c : 3;
                unsigned short d : 4;
                unsigned short e : 5;
        } field;
} v;

v.field.a = 1;
v.field.b = 2;
v.field.c = 3;
v.field.d = 4;
v.field.e = 5;对于上述代码,在大端平台和小端平台分别会得到什么呢?如下图:
小端平台:

大端平台:


事实上,如果你要在想要跨大端小端平台的代码上写一个靠谱的使用了位域的结构体的话,你需要通过宏来判断当前是大端还是小端,然后用不同的顺序去写结构体。典型的例子就是Linux内ip_hdr结构体的定义: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. */
};参考资料:
How Endianness Effects Bitfield Packing
C/C++: Force Bit Field Order and Alignment

watermelon 发表于 2018-11-15 00:52:45

啊,学习到了啊,文中这句:“可以看出:在小端平台上,顺序靠前的位域变量它的位置更靠右。而对于大端平台,顺序靠前的位域变量则更靠左一些。”
感觉加上说明:内存中“低地址------->高地址”就更加明白大端小端了
但是小弟不知道怎么在win32汇编中实现这个位域的数据结构,难道这个只有C/C++才有的么?

watermelon 发表于 2018-11-15 02:08:48

watermelon 发表于 2018-11-15 00:52
啊,学习到了啊,文中这句:“可以看出:在小端平台上,顺序靠前的位域变量它的位置更靠右。而对于大端平台 ...

了解了,tangptr大佬说有record关键字,的确可以。

Si515 发表于 2018-11-20 18:13:35

学习一下
页: [1]
查看完整版本: 【C】位域的相邻顺序