C 转换位字段结构上的Endianess(再次)

C 转换位字段结构上的Endianess(再次),c,endianness,bit-fields,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

以前有人问过这个问题,但我仍然有点困惑,当移动到一个具有相反端点的平台时(在这种情况下,从大到小),如何处理位场结构。如果我有这个:

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进行包装,但我不想在这里把东西弄得乱七八糟。

我想说,以下结构是不可移植的,因为它不会将内存中结构使用的位模式从小到大端或verse vica进行更改:

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

大端存储布局:

 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            ]
 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             ]
要在切换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);
typedef结构
{
#如果已定义(BIGENDIAN)
无符号短a:5;
无符号短b:1;
无符号短c0:2;
无符号短c1:3;
无符号短d:5;
#定义的elif(LITTLEENDIAN)
无符号短c1:3;
无符号短d:5;
无符号短a:5;
无符号短b:1;
无符号短c0:2;
#否则
#错误“不支持endianess”
#恩迪夫
}协议类型;

#定义pt_c(pt)(pt.c0&(pt.c1I将保留您原来的内容,但在引用之前将
word
的字节顺序颠倒(如果需要).

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

您有两个解决此问题的选项

第一种是健康剂量的
#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))
typedef无符号字符协议类型[2];
#定义摘录a(x)((x[0]&0xF8)>>3)
#定义摘录(x)((x[0]&0x04)>>2)
#定义摘录_c(x)((x[0]&0x03)>5))
#定义摘录(x)((x[1]&0x1F))

这需要使用getter/setter方法,但您可以避免大多数可移植性问题,因为您可以显式地为所有内容指定位顺序和字节顺序。

是的,正如您所示,您只需颠倒字段声明的顺序就可以了。我不认为有任何保证(或者甚至没有理由相信)位域中的位顺序遵循内存中的字节顺序。换句话说,仅仅因为你从一个小端机跳到了一个大端机,你不能假设你应该反转所有的位域。换句话说:位域对可移植性非常不利,应该在可移植的代码中避免。正如unwind提到的,位域非常重要不可移植。编译器在布局上有很大的自由度。我认为如果可以的话,最好将代码完全从位域移开。@TJD我不认为这是正确的。如果数据最初是作为位域写入的,那么字节顺序无关紧要-字节0位0将在两端的同一位置。如何如果它们在写出来之前被压缩到一个
int
short
中,那么endianness确实很重要——所以底线是我们可能需要知道更多。此外,endianness改变字节的顺序,而不是位,所以简单地颠倒整个列表是完全错误的。位字段是不可移植的。endianness、alignment、位顺序和填充位不是由标准定义的。此外,位字段只能是int类型,我相信使用任何其他类型都是未定义的行为。所以…真倒霉。如果您想要可移植代码,就应该使用位运算符。+1表示直接,这与我自己的解决方案一致…;-)使用
ntohs()的典型调用案例
/
htons()
,当然。我应该提到ntohs()我自己,nice one@alkI不知道ntohs/ntohl/htons/htonl。这样我就不用拥有自己的函数了。很好!在几乎所有的平台上,短的绝对是16位。短的长度取决于2个字节的顺序。长的则取决于4个字节的长度。现在,当你试着在一个位字段中加一个短字符,它必须是int类型的?猜猜看,因为它是未定义的行为。既然标准中没有定义位顺序和位字段的尾数,你是如何做到这一点的?而且,严格地说,位字段中可能有任意数量的填充位和填充字节。我知道同意您对缺少的标准的看法。当然,当需要移植到许多不同的平台时,这可能会导致麻烦。我提出的解决方案基于我在x86(VC、gcc)和s390x(gcc)上的经验和zOS.@LundinBitmasks/位运算符对任何CPU的任何C编译器都是100%可移植的,因此它们确实是最好的解决方案。另一方面,位字段是C标准中非常愚蠢的东西之一。
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))