【问题标题】:Convert Little Endian to Big Endian将小端转换为大端
【发布时间】:2013-10-17 01:02:58
【问题描述】:

我只是想问一下我从小端转换为大端的方法是否正确,只是为了确定我是否理解其中的区别。

我有一个以 little-endian 存储的数字,这里是数字的二进制和十六进制表示:

‭0001 0010 0011 0100 0101 0110 0111 1000‬

‭12345678‬

在大端格式中,我认为应该交换字节,如下所示:

1000 0111 0110 0101 0100 0011 0010 0001

‭87654321

这是正确的吗?

此外,下面的代码尝试执行此操作但失败了。有什么明显的错误或者我可以优化一些东西吗?如果代码不适合这种转换,您能否解释一下原因并展示执行相同转换的更好方法?

uint32_t num = 0x12345678;
uint32_t b0,b1,b2,b3,b4,b5,b6,b7;
uint32_t res = 0;

b0 = (num & 0xf) << 28;
b1 = (num & 0xf0) << 24;
b2 = (num & 0xf00) << 20;
b3 = (num & 0xf000) << 16;
b4 = (num & 0xf0000) << 12;
b5 = (num & 0xf00000) << 8;
b6 = (num & 0xf000000) << 4;
b7 = (num & 0xf0000000) << 4;

res = b0 + b1 + b2 + b3 + b4 + b5 + b6 + b7;

printf("%d\n", res);

【问题讨论】:

  • 您可以使用更好的位模式示例,例如“0001 0010 0011 0100 0101 0110 0111 1000”
  • 您的代码是基于半字节的(4 位)而不是基于字节的(8 位)。它采用 32 位值并颠倒半字节的顺序。我认为您希望基于字节完成 64 位值。此外,由于它们是移动的,而不是轮换的,因此这些班次将不起作用。因此,您将“从头到尾”丢失位。为了稍微整理一下,考虑使用数组而不是离散的b1b2 等。
  • 我这样做是基于以下任务:“一个由十六进制表示 (st uv wx yz) 表示的 32 位数值应记录在一个四字节字段中为 (st uv wx yz) 。”因此,如果我也这样做,而是采用 8 位(1 字节),它会起作用吗?
  • 您的示例是 64 位的。所以你真的是指 32 位?
  • 不要自己进行转换,大多数平台都提供了这样的功能:htobe32htonl等。如果你想要可移植性,请使用this之类的标头。

标签: c endianness


【解决方案1】:

OP 的示例代码不正确。

字节序转换适用于位和 8 位字节级别。大多数字节序问题处理字节级别。 OP 代码在 4 位半字节级别进行字节序更改。建议改为:

// Swap endian (big to little) or (little to big)
uint32_t num = 9;
uint32_t b0,b1,b2,b3;
uint32_t res;

b0 = (num & 0x000000ff) << 24u;
b1 = (num & 0x0000ff00) << 8u;
b2 = (num & 0x00ff0000) >> 8u;
b3 = (num & 0xff000000) >> 24u;

res = b0 | b1 | b2 | b3;

printf("%" PRIX32 "\n", res);

如果性能真的很重要,则需要了解特定的处理器。否则,就交给编译器吧。

[编辑] OP 添加了一条改变事物的评论。
“用十六进制表示(st uv wx yz)表示的32bit数值应记录在一个四字节字段中,为(st uv wx yz)。”

出现这种情况,32位数字的字节序是unknown,结果需要以little字节序存储在内存中。

uint32_t num = 9;
uint8_t b[4];
b[0] = (uint8_t) (num >>  0u);
b[1] = (uint8_t) (num >>  8u);
b[2] = (uint8_t) (num >> 16u);
b[3] = (uint8_t) (num >> 24u);

[2016 年编辑] 简化

...结果的类型是提升的左操作数的类型...。按位移位运算符 C11 §6.5.7 3

shift 常量(右操作数)之后使用u 与不使用它的结果相同。

b3 = (num & 0xff000000) >> 24u;
b[3] = (uint8_t) (num >> 24u);
// same as 
b3 = (num & 0xff000000) >> 24;
b[3] = (uint8_t) (num >> 24);

【讨论】:

  • 啊。现在我明白了。太好了 非常感谢!我假设我只在需要时将字节数组转换为 uint32 对吗?
  • @JeckyPorter 难以“转换字节数组”。相反,加入工会。 union JPEndian { uint32_t u32; uint8_t u8[4]; };JPEndian.u8[0] = (uint8_t) (num &gt;&gt; 0u); ... printf("%" PRIX32 "\n", JPEndian.u32);
【解决方案2】:

对不起,我的回答有点晚了,但似乎没有人提到内置函数来反转字节顺序,这在在性能方面非常重要

大多数现代处理器都是小端的,而所有网络协议都是大端的。这是历史,您可以find on Wikipedia. 了解更多信息,但这意味着我们的处理器在我们浏览互联网时会在小端和大端之间转换数百万次。

这就是为什么大多数架构都有专用的处理器指令来完成这项任务。对于 x86 架构,有 BSWAP 指令,对于 ARM,有 REV。这是反转字节顺序的最有效方法

为了避免在我们的 C 代码中进行汇编,我们可以使用内置函数。对于 GCC,有 __builtin_bswap32() 函数,对于 Visual C++,有 _byteswap_ulong()。在大多数架构上,这些函数将只生成一条处理器指令

这是一个例子:

#include <stdio.h>
#include <inttypes.h>

int main()
{
    uint32_t le = 0x12345678;
    uint32_t be = __builtin_bswap32(le);

    printf("Little-endian: 0x%" PRIx32 "\n", le);
    printf("Big-endian:    0x%" PRIx32 "\n", be);

    return 0;
}

这是它产生的输出:

Little-endian: 0x12345678
Big-endian:    0x78563412

这里是反汇编(没有优化,即-O0):

        uint32_t be = __builtin_bswap32(le);
   0x0000000000400535 <+15>:    mov    -0x8(%rbp),%eax
   0x0000000000400538 <+18>:    bswap  %eax
   0x000000000040053a <+20>:    mov    %eax,-0x4(%rbp)

确实只有一条BSWAP 指令。

所以,如果我们确实关心性能,我们应该使用那些内置函数来代替任何其他字节反转方法。只是我的 2 美分。

【讨论】:

    【解决方案3】:

    我认为你可以使用函数htonl()。网络字节序为大端。

    【讨论】:

    • 这可能是最好的答案,它可以避免在标准解决方案已经存在时重新发明轮子。
    • 这不适用于大端机器:stackoverflow.com/questions/21311435/…
    • 请注意,htonl() 不在标准 C 库中,遗憾的是需要重新实现。 Some 实现返回 uint32_t,其他 unsigned longhton32() 固定大小。
    【解决方案4】:

    “我交换每个字节对吗?” -> 是的,要在小端和大端之间进行转换,你只需给字节相反的顺序。 但起初意识到几件事:

    • uint32_t的大小为32bits,即4字节,即8个HEX数字
    • 掩码0xf检索最低4位,要检索8位,需要0xff

    所以如果你想用这种掩码交换 4 个字节的顺序,你可以:

    uint32_t res = 0;
    b0 = (num & 0xff) << 24;        ; least significant to most significant
    b1 = (num & 0xff00) << 8;       ; 2nd least sig. to 2nd most sig.
    b2 = (num & 0xff0000) >> 8;     ; 2nd most sig. to 2nd least sig.
    b3 = (num & 0xff000000) >> 24;  ; most sig. to least sig.
    res = b0 | b1 | b2 | b3 ;
    

    【讨论】:

      【解决方案5】:

      你可以这样做:

      int x = 0x12345678;
      
      x = ( x >> 24 ) | (( x << 8) & 0x00ff0000 )| ((x >> 8) & 0x0000ff00) | ( x << 24)  ; 
      
      printf("value = %x", x);  // x will be printed as 0x78563412
      

      【讨论】:

        【解决方案6】:

        解决这个问题的一种稍微不同的方法有时很有用,就是将 16 位或 32 位值与一个字符数组结合起来。当我收到带有大端顺序的串行消息时,我一直在这样做,但我正在研究一个小端微。

        union MessageLengthUnion
        {
        
            uint16_t asInt;
            uint8_t asChars[2];
        
        };
        

        然后,当我收到消息时,我将第一个收到的 uint8 放入 .asChars[1] 中,第二个放入 .asChars[0] 中,然后我在程序的其余部分中将其作为联合的 .asInt 部分进行访问。

        如果您要存储一个 32 位的值,则可以将数组设为 4 长。

        【讨论】:

          【解决方案7】:

          我假设你在 linux 上

          包括"byteswap.h" & 使用int32_t bswap_32(int32_t argument);

          是逻辑视图,实际看,/usr/include/byteswap.h

          【讨论】:

            【解决方案8】:

            还有一个建议:

            unsigned int a = 0xABCDEF23;
            a = ((a&(0x0000FFFF)) << 16) | ((a&(0xFFFF0000)) >> 16);
            a = ((a&(0x00FF00FF)) << 8) | ((a&(0xFF00FF00)) >>8);
            printf("%0x\n",a);
            

            【讨论】:

            • 如何解释它的工作原理以及这种方法有什么好处?
            • 在第一条指令中它交换 16 位集(即字长),在第二条指令中它交换 8 位集(即字符长度),从而导致大端到小端的转换,反之亦然。输出将是:23EFCDAB。是的,好处是没有额外的变量和更少的步骤
            【解决方案9】:

            一个从小到大的简单C程序

            #include <stdio.h>
            
            int main() {
            unsigned int little=0x1234ABCD,big=0;
            unsigned char tmp=0,l;
            
            printf(" Little endian little=%x\n",little);
            
            for(l=0;l < 4;l++) 
            {
                tmp=0;
                tmp = little | tmp;
                big = tmp | (big << 8);
                little = little >> 8;
            }
            printf(" Big endian big=%x\n",big);
            
            return 0;
            }
            

            【讨论】:

              【解决方案10】:

              OP的代码不正确,原因如下:

              • 交换是在半字节(4 位)边界而不是字节(8 位)边界上执行的。
              • 最后四次交换的左移 &lt;&lt; 操作不正确,它们应该是右移 &gt;&gt; 操作,并且它们的移位值也需要更正。
              • 不需要使用中间存储,因此可以重写代码以更简洁/可识别。这样做,一些编译器将能够通过识别常用模式来更好地优化代码。

              考虑以下代码,它可以有效地转换无符号值:

              // Swap endian (big to little) or (little to big)
              uint32_t num = 0x12345678;
              uint32_t res =
                  ((num & 0x000000FF) << 24) |
                  ((num & 0x0000FF00) << 8) |
                  ((num & 0x00FF0000) >> 8) |
                  ((num & 0xFF000000) >> 24);
              
              printf("%0x\n", res);
              

              结果在这里以二进制和十六进制表示,注意字节是如何交换的:

              ‭0111 1000 0101 0110 0011 0100 0001 0010‬
              
              78563412
              

              优化

              在性能方面,尽可能让编译器优化您的代码。对于这样的简单算法,您应该避免不必要的数据结构,例如数组,这样做通常会导致不同的指令行为,例如访问 RAM 而不是使用 CPU 寄存器。

              【讨论】:

                【解决方案11】:

                您可以使用 lib 函数。它们归结为汇编,但如果您对 C 中的替代实现持开放态度,那么它们就是(假设 int 是 32 位):

                void byte_swap16(unsigned short int *pVal16) {
                
                //#define method_one 1
                // #define method_two 1
                #define method_three 1
                #ifdef method_one
                    unsigned char *pByte;
                
                    pByte = (unsigned char *) pVal16;
                    *pVal16 = (pByte[0] << 8) | pByte[1];
                #endif
                
                #ifdef method_two
                    unsigned char *pByte0;
                    unsigned char *pByte1;
                
                    pByte0 = (unsigned char *) pVal16;
                    pByte1 = pByte0 + 1;
                    *pByte0 = *pByte0 ^ *pByte1;
                    *pByte1 = *pByte0 ^ *pByte1;
                    *pByte0 = *pByte0 ^ *pByte1;
                #endif
                
                #ifdef method_three
                    unsigned char *pByte;
                
                    pByte = (unsigned char *) pVal16;
                    pByte[0] = pByte[0] ^ pByte[1];
                    pByte[1] = pByte[0] ^ pByte[1];
                    pByte[0] = pByte[0] ^ pByte[1];
                #endif
                
                
                }
                
                
                
                void byte_swap32(unsigned int *pVal32) {
                
                #ifdef method_one
                    unsigned char *pByte;
                
                    // 0x1234 5678 --> 0x7856 3412  
                    pByte = (unsigned char *) pVal32;
                    *pVal32 = ( pByte[0] << 24 ) | (pByte[1] << 16) | (pByte[2] << 8) | ( pByte[3] );
                #endif
                
                #if defined(method_two) || defined (method_three)
                    unsigned char *pByte;
                
                    pByte = (unsigned char *) pVal32;
                    // move lsb to msb
                    pByte[0] = pByte[0] ^ pByte[3];
                    pByte[3] = pByte[0] ^ pByte[3];
                    pByte[0] = pByte[0] ^ pByte[3];
                    // move lsb to msb
                    pByte[1] = pByte[1] ^ pByte[2];
                    pByte[2] = pByte[1] ^ pByte[2];
                    pByte[1] = pByte[1] ^ pByte[2];
                #endif
                }
                

                并且用法是这样执行的:

                unsigned short int u16Val = 0x1234;
                byte_swap16(&u16Val);
                unsigned int u32Val = 0x12345678;
                byte_swap32(&u32Val);
                

                【讨论】:

                  【解决方案12】:

                  以下是对我有用的另一种方法

                  convertLittleEndianByteArrayToBigEndianByteArray (byte littlendianByte[], byte bigEndianByte[], int ArraySize){
                      int i =0;
                  
                      for(i =0;i<ArraySize;i++){
                        bigEndianByte[i] = (littlendianByte[ArraySize-i-1] << 7 & 0x80) | (littlendianByte[ArraySize-i-1] << 5 & 0x40) |
                                              (littlendianByte[ArraySize-i-1] << 3 & 0x20) | (littlendianByte[ArraySize-i-1] << 1 & 0x10) |
                                              (littlendianByte[ArraySize-i-1] >>1 & 0x08) | (littlendianByte[ArraySize-i-1] >> 3 & 0x04) |
                                              (littlendianByte[ArraySize-i-1] >>5 & 0x02) | (littlendianByte[ArraySize-i-1] >> 7 & 0x01) ;
                      }
                  }
                  

                  【讨论】:

                    【解决方案13】:

                    下面的程序根据需要产生结果:

                    #include <stdio.h>
                     
                    unsigned int Little_To_Big_Endian(unsigned int num);
                     
                    int main( )
                    {
                        int num = 0x11223344 ;
                        
                        printf("\n Little_Endian = 0x%X\n",num);
                        
                        printf("\n Big_Endian    = 0x%X\n",Little_To_Big_Endian(num));
                     
                    }
                     
                    unsigned int Little_To_Big_Endian(unsigned int num)
                    {
                        return (((num >> 24) & 0x000000ff) | ((num >> 8) & 0x0000ff00) | ((num << 8) & 0x00ff0000) | ((num << 24) & 0xff000000));
                    }
                    

                    还可以使用以下功能:

                        unsigned int Little_To_Big_Endian(unsigned int num)
                        {
                            return (((num & 0x000000ff) << 24) | ((num & 0x0000ff00) << 8 ) | ((num & 0x00ff0000) >> 8) | ((num & 0xff000000) >> 24 ));
                        }
                    

                    【讨论】:

                      【解决方案14】:
                      #include<stdio.h>
                      int main(){
                              int var = 0X12345678;
                              var =  ((0X000000FF & var)<<24)|
                                     ((0X0000FF00 & var)<<8) |
                                     ((0X00FF0000 & var)>>8) |
                                     ((0XFF000000 & var)>>24);
                              printf("%x",var);
                      
                      }
                      

                      【讨论】:

                        【解决方案15】:

                        这是我编写的一个小功能,效果很好,它可能无法移植到每台机器或单个 cpu 指令的速度,但应该适用于大多数人。它可以处理高达 32 字节(256 位)的数字,并且适用于大端和小端交换。关于这个函数最好的部分是你可以将它指向一个字节数组,在转换前将其插入或连接,并交换内联字节。

                        #include <stdio.h>
                        #include <string.h>
                        
                        void byteSwap(char**,int);
                        
                        int main() {
                        
                            //32 bit
                            int test32 = 0x12345678;
                            printf("\n BigEndian = 0x%X\n",test32);
                        
                            char* pTest32 = (char*) &test32;
                        
                            //convert to little endian
                            byteSwap((char**)&pTest32, 4);
                            printf("\n LittleEndian = 0x%X\n", test32);
                        
                            //64 bit
                            long int test64 = 0x1234567891234567LL;
                            printf("\n BigEndian = 0x%lx\n",test64);
                        
                            char* pTest64 = (char*) &test64;
                        
                            //convert to little endian
                            byteSwap((char**)&pTest64,8);
                            printf("\n LittleEndian = 0x%lx\n",test64);
                        
                            //back to big endian
                            byteSwap((char**)&pTest64,8);
                            printf("\n BigEndian = 0x%lx\n",test64);
                        
                            return 0;
                        }
                        
                        
                        void byteSwap(char** src,int size) {
                            int x = 0;
                            char b[32];
                            while(size-- >= 0) { b[x++] = (*src)[size]; };
                            memcpy(*src,&b,x);
                        }
                        

                        输出:

                        $gcc -o main *.c -lm
                        $main
                        
                         BigEndian = 0x12345678
                        
                         LittleEndian = 0x78563412
                        
                         BigEndian = 0x1234567891234567
                        
                         LittleEndian = 0x6745239178563412
                        
                         BigEndian = 0x1234567891234567
                        

                        【讨论】:

                          猜你喜欢
                          • 2018-10-19
                          • 2019-12-03
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-02-16
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多