【问题标题】:C/C++ code to convert big endian to little endian将大端转换为小端的 C/C++ 代码
【发布时间】:2017-12-31 15:05:39
【问题描述】:

我见过几个不同的代码示例,它们可以将大端转换为小端,反之亦然,但我遇到过有人编写的一段代码,似乎可以工作,但我'我不知道为什么会这样。

基本上,有一个 char 缓冲区,在某个位置,包含一个 4 字节的 int 存储为 big-endian。该代码将提取整数并将其存储为本机小端。这是一个简短的例子:

char test[8] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
char *ptr = test;
int32_t value = 0;
value =  ((*ptr) & 0xFF)       << 24;
value |= ((*(ptr + 1)) & 0xFF) << 16;
value |= ((*(ptr + 2)) & 0xFF) << 8;
value |= (*(ptr + 3)) & 0xFF;
printf("value: %d\n", value);

值:66051

上面的代码获取前四个字节,将其存储为小端,并打印结果。谁能逐步解释这是如何工作的?我很困惑为什么 ((*ptr) & 0xFF) = 8 都不会评估为 0。

【问题讨论】:

  • 因为在算术完成之前char 值被提升为int。注意:应该使用unsigned char *uint32_t
  • 您的代码独立于字节序,它将在大小端机器上打印66051value 存储在机器的字节序中,并不总是存储在小字节序中。
  • &amp; 0xFF 仅对有符号值是必需的,当负的 char 值符号扩展为 int 时,去除多余的位。使用unsigned 的原因之一,以及可疑的符号位转换。
  • 在 x86 上可以使用 ntohl。
  • @WeatherVane:使用有符号字符和有符号整数确实不美观,但不会改变这个交换过程的任何功能。

标签: c endianness


【解决方案1】:

这段代码正在构造值,一次一个字节。

首先它捕获最低字节

 (*ptr) & 0xFF

然后移到最高字节

 ((*ptr) & 0xFF) << 24

然后将它赋给之前初始化的0值。

 value =((*ptr) & 0xFF) << 24

现在“魔法”开始发挥作用。由于ptr 值被声明为char*,因此在其上加一会使指针前进一个字符。

 (ptr + 1) /* the next character address */
 *(ptr + 1) /* the next character */

在您看到他们使用指针数学来更新相对起始地址之后,其余操作与已经描述的操作相同,只是为了保留部分移位的值,他们将or 的值放入现有的value 变量

 value |= ((*(ptr + 1)) & 0xFF) << 16

请注意,指针数学是您可以执行类似操作的原因

 char* ptr = ... some value ...

 while (*ptr != 0) {
     ... do something ...
     ptr++;
 }

但它的代价是可能真的弄乱了您的指针地址,大大增加了违反 SEGFAULT 的风险。一些语言认为这是一个问题,他们删除了进行指针数学的能力。您无法对其进行指针数学运算的几乎指针通常称为引用。

【讨论】:

    【解决方案2】:

    如果你想将小端表示转换为大端,你可以使用 htonl、htons、ntohl、ntohs。这些函数在主机和网络字节顺序之间转换值。 Big endian 也用于基于 arm 的平台。看这里:https://linux.die.net/man/3/endian

    【讨论】:

    • 据我所知,ARM 的默认字节序是小字节序。大多数都用于小端,但您可以切换到大端(在任何项目中从未见过)。 ARM 和 Intel 自 x486 以来提供本机支持交换指令。见这里:infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0210c/…
    • 此答案仅对在 little-endian 主机上运行的代码正确。对于在大端主机上运行的代码(想到 HP-UX),htonl() 和朋友都是无操作的。
    • 为什么没有整数相等?
    【解决方案3】:

    您可能使用的代码基于网络上的号码应以 BIG ENDIAN 模式发送的想法。

    htonl()htons() 函数在 BIG ENDIAN 中转换 32 位整数和 16 位整数,其中您的系统使用 LITTLE ENDIAN,否则它们会将数字保留在 BIG ENDIAN 中。

    代码如下:

    #include <stdio.h>
    #include <stdint.h>
    #include <inttypes.h>
    #include <arpa/inet.h>
    
    int main(void)
    {
        uint32_t x,y;
        uint16_t s,z;
    
        x=0xFF567890;
    
        y=htonl(x);
    
        printf("LE=%08X BE=%08X\n",x,y);
    
        s=0x7891;
    
        z=htons(s);
    
        printf("LE=%04X BE=%04X\n",s,z);
    
        return 0;
    
    }
    

    编写此代码是为了在 LE 机器上从 LE 转换为 BE。

    您可以使用相反的函数 ntohl()ntohs() 将 BE 转换为 LE,这些函数在 LE 机器上将整数从 BE 转换为 LE,而不是在 BE 机器上转换。

    【讨论】:

      【解决方案4】:

      我很困惑为什么 ((*ptr) & 0xFF) = 8 都不会计算为 0。

      我认为您误解了换档功能。

      value = ((*ptr) & 0xFF) << 24;
      

      表示用 0xff(字节)屏蔽 ptr 处的值,然后移位 24 位(不是字节)。即移动 24/8 个字节(3 个字节)到最高字节。

      【讨论】:

        【解决方案5】:

        理解((*ptr) &amp; 0xFF) &lt;&lt; X评价的关键之一

        Integer Promotion。值 (*ptr) &amp; 0xff 在移动之前提升为 Integer

        【讨论】:

          【解决方案6】:

          我已经编写了下面的代码。此代码包含两个函数swapmem()swap64()

          • swapmem() 交换任意维度内存区域的字节。

          • swap64() 交换 64 位整数的字节。

          在这个回复的最后,我告诉你一个解决字节缓冲区问题的想法。

          代码如下:

          #include <stdio.h>
          #include <stdint.h>
          #include <inttypes.h>
          #include <malloc.h>
          
          void * swapmem(void *x, size_t len, int retnew);
          uint64_t swap64(uint64_t k);
          
          /**
              brief swapmem
          
                   This function swaps the byte into a memory buffer.
          
              param x
                   pointer to the buffer to be swapped
          
              param len
                   lenght to the buffer to be swapped
          
              param retnew
                   If this parameter is 1 the buffer is swapped in a new
                   buffer. The new buffer shall be deallocated by using
                   free() when it's no longer useful.
          
                   If this parameter is 0 the buffer is swapped in its
                   memory area.
          
              return
                  The pointer to the memory area where the bytes has been
                  swapped or NULL if an error occurs.
          */
          void * swapmem(void *x, size_t len, int retnew)
          {
              char *b = NULL, app;
              size_t i;
          
              if (x != NULL) {
                  if (retnew) {
                      b = malloc(len);
                      if (b!=NULL) {
                          for(i=0;i<len;i++) {
                              b[i]=*((char *)x+len-1-i);
                          }
                      }
                  } else {
                      b=(char *)x;
                      for(i=0;i<len/2;i++) {
                          app=b[i];
                          b[i]=b[len-1-i];
                          b[len-1-i]=app;
                      }
                  }
              }
              return b;
          }
          
          uint64_t swap64(uint64_t k)
          {
              return ((k << 56) |
                      ((k & 0x000000000000FF00) << 40) |
                      ((k & 0x0000000000FF0000) << 24) |
                      ((k & 0x00000000FF000000) << 8) |
                      ((k & 0x000000FF00000000) >> 8) |
                      ((k & 0x0000FF0000000000) >> 24)|
                      ((k & 0x00FF000000000000) >> 40)|
                      (k >> 56)
                     );
          }
          
          int main(void)
          {
              uint32_t x,*y;
              uint16_t s,z;
              uint64_t k,t;
          
              x=0xFF567890;
          
              /* Dynamic allocation is used to avoid to change the contents of x */
              y=(uint32_t *)swapmem(&x,sizeof(x),1);
              if (y!=NULL) {
                  printf("LE=%08X BE=%08X\n",x,*y);
                  free(y);
              }
          
              /* Dynamic allocation is not used. The contents of z and k will change */
              z=s=0x7891;
              swapmem(&z,sizeof(z),0);
              printf("LE=%04X BE=%04X\n",s,z);
          
              k=t=0x1120324351657389;
              swapmem(&k,sizeof(k),0);
              printf("LE=%16"PRIX64" BE=%16"PRIX64"\n",t,k);
          
              /* LE64 to BE64 (or viceversa) using shift */
              k=swap64(t);
              printf("LE=%16"PRIX64" BE=%16"PRIX64"\n",t,k);
          
              return 0;
          }
          

          程序编译完成后,我好奇地看到了 gcc 生成的汇编代码。我发现函数swap64的生成如下所示。

          00000000004007a0 <swap64>:
            4007a0:       48 89 f8                mov    %rdi,%rax
            4007a3:       48 0f c8                bswap  %rax
            4007a6:       c3                      retq
          

          此结果是在具有 Intel I3 CPU 的 PC 上使用 gcc 选项编译代码获得的:-Ofast,或 -O3,或 -O2,或 -Os。

          您可以使用类似swap64() 的函数来解决您的问题。我命名为swap32()

          uint32_t swap32(uint32_t k)
          {
              return ((k << 24) |
                      ((k & 0x0000FF00) << 8) |
                      ((k & 0x00FF0000) >> 8) |
                      (k >> 24)
                     );
          }
          

          您可以将其用作:

          uint32_t j=swap32(*(uint32_t *)ptr);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-03-12
            • 1970-01-01
            • 2013-10-17
            • 1970-01-01
            • 2018-10-19
            • 1970-01-01
            相关资源
            最近更新 更多