将大端转换为小端的 C/C++ 代码

Posted

技术标签:

【中文标题】将大端转换为小端的 C/C++ 代码【英文标题】:C/C++ code to convert big endian to little endian 【发布时间】: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:使用有符号字符和有符号整数确实不美观,但不会改变这个交换过程的任何功能。 【参考方案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);

【讨论】:

以上是关于将大端转换为小端的 C/C++ 代码的主要内容,如果未能解决你的问题,请参考以下文章

从二进制文件读取时将大端转换为小端

在C中将char从大端转换为小端

实现大小端之间的转换宏,包括32位和64位的数

判断系统是大端还是小端的两种方法

关于摩托罗拉的can协议(处理器为大端,协议为小端)

轻松记住大端小端的含义(附对大端和小端的解释)