【问题标题】:Converting Endianess on a bit field structure在位域结构上转换字节序
【发布时间】:2009-04-07 06:58:08
【问题描述】:

我需要将位域结构从小端架构转换为大端架构。 如果我只是交换结构元素,那么最好的方法是什么,因为字节边界会出现问题。

前结构为:

struct {
    unsigned int    b1:1;
    unsigned int    b2:8;
    unsigned int    b3:7;
    unsigned int    b4:8;
    unsigned int    b5:7;
    unsigned int    b6:1;
}; 

【问题讨论】:

  • 您的问题足以回答我关于单独问题的问题 - 谢谢! :)

标签: c data-structures endianness


【解决方案1】:

您可以使用 32 位整数,并使用 and- 和 bitshift 运算符从中提取信息。有了它,您可以简单地使用 htonl(主机到网络,长)。网络字节序为大端。

这不会像位域那样优雅,但至少您会知道自己拥有什么,并且不必担心编译器会填充您的结构。

【讨论】:

  • +1 对我来说,htonl() 或 htons() 结合位掩码和位移位是这类东西最易于维护的方法。
  • 是的,你是对的,虽然下面给出的 epatel 方法也有效,但我只需要看看它在哪里不起作用:)
  • epatel 给出的方法也很常见(我也赞成)。但是当位域与字节边界重叠时可能会很棘手。
  • 请记住,这仅适用于 Windows。除此之外,您还必须包含所有的 winsock 库 (ws2_32.lib),这会给您的项目增加不合理的开销。
【解决方案2】:

处理器字节序与位字段排序无关。同一台计算机上的两个编译器很可能对位域使用相反的顺序。所以,鉴于此:

union {
    unsigned char x;
    struct {
        unsigned char b1 : 1;
        unsigned char b2 : 7;
    };
} abc;
abc.x = 0;
abc.b1 = 1;
printf( "%02x\n", abc.x );

除非您碰巧有详细的文档,否则要知道会打印出 01 还是 80 的唯一方法就是尝试一下。

【讨论】:

    【解决方案3】:

    在将代码从 MIPS 移植到 Linux/x86 的项目中,我们这样做了。

    struct {
    
    #ifdef __ONE_ENDIANESS__
        unsigned int    b1:1;
        unsigned int    b2:8;
        unsigned int    b3:7;
        unsigned int    b4:8;
        unsigned int    b5:7;
        unsigned int    b6:1;
    #define _STRUCT_FILLED
    #endif /* __ONE_ENDIANESS__ */
    
    #ifdef __OTHER_ENDIANESS__
        unsigned int    b6:1;
        unsigned int    b5:7;
        unsigned int    b4:8;
        unsigned int    b3:7;
        unsigned int    b2:8;
        unsigned int    b1:1;
    #define _STRUCT_FILLED
    #endif /* __OTHER_ENDIANESS__ */
    
    };
    
    #ifndef _STRUCT_FILLED
    #  error Endianess uncertain for struct
    #else
    #  undef _STRUCT_FILLED
    #endif /* _STRUCT_FILLED */
    

    __ONE_ENDIANESS____OTHER_ENDIANESS__ 适合我们使用的编译器,因此您可能需要查看哪个适合您...

    【讨论】:

    • 请注意,在第一个示例中,字段 b2 和 b5 跨越多个字节,因此在第二种情况下它们不太可能被重写以匹配。否则,这个技巧可以节省大量的头发拉扯。
    • @epatel: sizeof (int) 肯定是 4,还是类似的小值? sizeof 的单位是 chars,因此对于 CHAR_BIT 为 8 的机器上的 32 位 int,它将给出 4。
    • 如果一个字段长于 8 位,它也应该被交换为一个字节序结构
    • @epatel 这对我来说似乎是最好的出路!
    • @epatel: sizeof(int) == 32 在 32 位拱门上。在大多数 64 位架构上它是 64。
    【解决方案4】:

    那里有两个 16 位部分(前三个字段和后三个字段是 16 位)。

    只有 65536 个条目。所以有一个查找表来保存字段的位反转版本。将该结构与另一个具有两个 16 位字段的结构包装在一个联合中以使其更容易?

    类似的东西(未经测试,我不在 C 编译器附近):

    union u {
        struct {
            unsigned int    b1:1;
            unsigned int    b2:8;
            unsigned int    b3:7;
            unsigned int    b4:8;
            unsigned int    b5:7;
            unsigned int    b6:1;
         } bits;
         struct {
            uint16 first;
            uint16 second;
         } words
    } ;
    
    unit16 lookup[65536];
    
    /* swap architectures */
    
    void swapbits ( union u *p)
    {
       p->words.first = lookup[p->words.first];
       p->words.second = lookup[p->words.second];
    }
    

    查找表的数量留给读者作为练习:)

    但是,请仔细阅读您的编译器文档。我不确定 C 标准是否要求该结构适合一个单词(尽管我希望大多数编译器都这样做)。

    【讨论】:

    • 除非性能绝对关键,否则这段代码会浪费 128k 内存。难怪 4Gb 不再被认为足以用于生产性工作 ;-)
    • 好吧,也许这很关键,我们还没有被告知。
    【解决方案5】:

    您希望在通道(文件或网络)和您的结构之间执行此操作。我的首选做法是通过编写以已知表示形式构建文件缓冲区的代码并匹配反转该转换的读取代码来将文件 I/O 与结构隔离。

    您的具体示例特别难以猜测,因为位域被定义为 unsigned int 并且sizeof(unsigned int) 特别不可移植。

    假设 sizeof(int)==4 是一个 SWAG,然后获取指向结构的指针并重新排列各个字节可能会得到你想要的答案。

    为不同平台定义不同结构的技巧可能有效,但在您引用的示例中,字节边界没有完全中断,因此不太可能产生相当于一个平台在另一个平台上,而不是将一个或多个字段分成两部分。

    【讨论】:

      【解决方案6】:

      交换字节应该足够了。字节内的位位置在大端和小端中是相同的。
      例如:

      char* dest = (char*)&yourstruct;
      unsigned int orig = yourstruct;
      char* origbytes = (char*)&orig;
      dest[0] = origbytes[3];
      dest[1] = origbytes[2];
      dest[2] = origbytes[1];
      dest[3] = origbytes[0];
      

      【讨论】:

      • 据我所知,ANSI C 标准没有指定位域在字节(或字)内分配的顺序,因此交换字节可能不够。
      • 是的,不可移植。但我 大多数编译器应该将这些位放在“自然”的位置(例如 struct {unsigned char a:1,b:6,c:1} ---> a bit 0, b bit 1-6,c 位 7。)...如果可移植性最重要,请使用 roe 的建议。
      【解决方案7】:

      当物理布局很重要时,您不应该使用位域,因为它是由实现定义的,以哪个顺序填充较大的字。

      【讨论】:

      • 通常包头(物理布局非常重要)使用位字段来定义子字节字段。以 linux 内核的 IP 头在 netinet/ip.h(或lxr.linux.no/linux+v2.6.38/include/linux/ip.h#L80)为例
      • 嗯,那又怎样?他们正在为特定的编译器 (gcc) 编写代码。
      • 这不能回答 OP 的问题; OP 会考虑到这一点。
      【解决方案8】:

      为了实现这一点,我终于找到了一个解决方案(一些源自上述 epatel 解决方案的方法)。这是如果我从 x86 转换到 Solaris SPARC。

      我们需要先交换传入的结构,然后以相反的顺序读取元素。 基本上在查看了结构是如何对齐的之后,我发现字节顺序和位顺序都发生了变化。这是一个伪代码。

      struct orig
      {    
          unsigned int    b1:1;
          unsigned int    b2:8;
          unsigned int    b3:7;
          unsigned int    b4:8;
          unsigned int    b5:7;
          unsigned int    b6:1;
      };
      
      struct temp
      {    
          unsigned int    b6:1;
          unsigned int    b5:7;
          unsigned int    b4:8;
          unsigned int    b3:7;
          unsigned int    b2:8;
          unsigned int    b1:1;
      }temp;
      
      
      func (struct orig *toconvert)
      {
          struct temp temp_val;
          //Swap the bytes
          swap32byte((u32*)toconvert);
          //Now read the structure in reverse order - bytes have been swapped
          (u32*)&temp_val = (u32 *)toconvert;
          //Write it back to orignal structure
          toconvert->b6=temp_val.b6;
          toconvert->b5=temp_val.b5;
          toconvert->b4=temp_val.b4;
          toconvert->b3=temp_val.b3;
          toconvert->b2=temp_val.b2;
          toconvert->b1=temp_val.b1;
      

      }

      经过一些实验,我发现这种方法只有在元素完全填充结构时才有效,即没有未使用的位。

      【讨论】:

      • 我敢肯定这是因为这是个坏主意,但我登陆这个页面是因为我不熟悉位字段并且我不知道 为什么 它是一个坏主意。有人可以解释一下吗?
      猜你喜欢
      • 2020-09-09
      • 2011-02-11
      • 1970-01-01
      • 2015-11-18
      • 1970-01-01
      • 1970-01-01
      • 2012-11-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多