在位域结构上转换Endianess(再次)

时间:2012-08-22 14:54:50

标签: c endianness bit-fields

之前曾问过这个问题,但是当我转移到具有相反字节序的平台时(在这种情况下,从大到小),我仍然对如何处理位域结构感到困惑。如果我有这个:

typedef struct
{
    unsigned short a :5;
    unsigned short b :1;
    unsigned short c :5;
    unsigned short d :5;
} protocol_type;

typedef union
{
  protocol_type cmd;
  unsigned short word;
}protocol_cmd_type;

这样处理这个问题的正确方法是什么?

typedef struct
{
    unsigned short d :5;
    unsigned short c :5;
    unsigned short b :1;
    unsigned short a :5;
} protocol_type;

typedef union
{
  protocol_type cmd;
  unsigned short word;
}protocol_cmd_type;

还是其他什么?

这就是我所做的,但它没有给出我期待的结果。但是这个代码还有其他问题,所以我不确定上面是否真的错了。希望能在这里获得洞察力,这样我就可以将这部分列入清单。

实际上我需要让代码在两个平台上都能正常工作,所以我要把它们包装在#defines周围,但我不想把这些东西弄得乱七八糟。

3 个答案:

答案 0 :(得分:2)

我会保留你原来的内容,但在引用之前反转word的字节顺序(如果需要)。

答案 1 :(得分:2)

在这里,您需要担心的更多是关于endianess问题。请注意,C标准并未定义如何在内存中布置位域的详细信息,这意味着两个编译器可以生成不同的结果,即使它们是针对具有相同endianess的平台。有些可能会将列出的第一个位字段视为最低地址位,而其他位可能会将其视为最高地址位。

您有两种方法可以解决此问题。

第一种是健康剂量#ifdef

typedef struct
{
#ifdef CPU_IS_BIG_ENDIAN
    unsigned short a :5;
    unsigned short b :1;
    unsigned short c :5;
    unsigned short d :5;
#else
    unsigned short d :5;
    unsigned short c :5;
    unsigned short b :1;
    unsigned short a :5;
#endif
} protocol_type;

这导致了凌乱的结构定义,但允许其余代码保持干净。由于您具有跨越字节边界的字段,因此您必须为每个目标体系结构/平台提供新的结构定义(可能通过反复试验)。如果您必须支持多个编译器,这些编译器对同一平台的位域命名方式不同,那么您的定义将变得更加复杂。

另一种选择是完全避免使用位域并改为使用位掩码:

typedef unsigned char protocol_type[2];
#define extract_a(x) ((x[0] & 0xF8) >> 3)
#define extract_b(x) ((x[0] & 0x04) >> 2)
#define extract_c(x) (((x[0] & 0x03) << 3) | ((x[1] & 0xE0) >> 5))
#define extract_d(x) ((x[1] & 0x1F))

这需要使用getter / setter方法,但是你要避免大多数可移植性问题,因为你明确指定了所有内容的位和字节顺序。

答案 2 :(得分:1)

我认为以下结构可移植性,不会改变内存中结构使用的位模式,从小端到大端或者维也纳vica:

typedef struct
{
    unsigned short a :5;
    unsigned short b :1;
    unsigned short c :5;
    unsigned short d :5;
} protocol_type;

证明:

Big endian内存布局:

 d4   d3   d2   d1   d0   c4   c3   c2   c1   c0   b0   a4   a3   a2   a1  a0
<-             byte 1                -> <-              byte 0              ->  
MSB                                 LSB MSB                                LSB
[              address 1              ] [               address 0            ]

Little endian内存布局:

 c1   c0   b0   a4   a3   a2   a1  a0   d4   d3   d2   d1   d0   c4   c3   c2 
<-             byte 0               -> <-              byte 1               ->  
MSB                                LSB MSB                                 LSB
[              address 1             ] [               address 0             ]

从此我不知道如何重新排序abcd以在小端和大端上形成相同的位模式机器。原因是结构的成员c越过字节边界。


以下结构可以是便携式的:

typedef struct
{
    unsigned short e :5;
    unsigned short f :3;
    unsigned short g :3;
    unsigned short h :5;
} protocol_type;

在切换endianess时保持位模式在内存中只需修改它:

typedef struct
{
    unsigned short g :3;
    unsigned short h :5;
    unsigned short e :5;
    unsigned short f :3;
} protocol_type;

OP问题的一个可能解决方案是按以下方式修改结构:

typedef struct
{
#if defined(BIGENDIAN)
        unsigned short a :5;
        unsigned short b :1;
        unsigned short c0 :2;
        unsigned short c1 :3;
        unsigned short d :5;
#elif defined(LITTLEENDIAN)
        unsigned short c1 :3;
        unsigned short d :5;
        unsigned short a :5;
        unsigned short b :1;
        unsigned short c0 :2;
#else
#error "endianess not supported"
#endif
} protocol_type;


#define pt_c(pt) (pt.c0 & (pt.c1 << 2))

foo(void)
{
   protocol_type pt;

   ... /* some assignment to pt ... */
   /* to then access the original value of member c use the macro */

   unsigned short c = pt_c(pt);