将小端序转换为大端序

28

我只是想问一下,我将小端存储转换成大端的方法是否正确,只是为了确保我理解了它们之间的区别。

我有一个以小端存储的数字,以下是该数字的二进制和十六进制表示:

‭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);

2
你可以使用更好的示例位模式,例如“0001 0010 0011 0100 0101 0110 0111 1000”。 - chux - Reinstate Monica
8
你的代码是基于四位(4-bit)的二进制数据而非八位(8-bit)的字节数据。它将32位值的各个四位进行颠倒排列。我认为你要使用基于字节的64位数值处理。此外,由于移位操作不是旋转操作,所以它们无法正常工作。因此,你将失去“超出末尾”的位数。另外,为了让代码更整洁,可以考虑使用数组而不是离散的b1,b2等变量。 - lurker
1
我正在根据以下任务进行操作:“将由十六进制表示法(st uv wx yz)表示的32位数值记录在四字节字段中,如(st uv wx yz)所示。”那么,如果我使用8位(1字节)而不是32位,它会起作用吗? - JeckyPorter
是的,这是一个32位的示例。可能是我的错误,我拿了64位而不是32位。但它肯定应该是32位的。 - JeckyPorter
3
不要自己进行转换,大多数平台都提供了处理此类操作的函数:htobe32htonl等。如果需要可移植性,请使用类似这样的头文件。 - legends2k
显示剩余2条评论
16个回答

48

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);

如果性能真的很重要,就需要知道特定的处理器。否则,就让编译器去处理。

[编辑] 问题的提出者新增了一条评论。
“32位数值使用十六进制表示(st uv wx yz)应该以(st uv wx yz)的形式记录在四字节字段中。”

在这种情况下,32位数字的字节序是未知的,结果需要以小端顺序存储在内存中。

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

位移 常量(右操作数)之后使用 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
@JeckyPorter 很难将字节数组“转换”。相反,使用联合体。 union JPEndian { uint32_t u32; uint8_t u8[4]; }; JPEndian.u8[0] = (uint8_t) (num >> 0u); ... printf("%" PRIX32 "\n", JPEndian.u32); - chux - Reinstate Monica

46

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

大多数现代处理器都是小端的,而所有网络协议都是大端的。那是历史,在此你可以找到更多信息:维基百科。但这意味着我们的处理器在浏览互联网时需要在小端和大端之间进行数百万次的转换。

这就是为什么大多数体系结构都有专门的处理器指令来简化这个任务。对于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指令。

因此,如果我们关心性能,我们应该使用这些内置函数而不是任何其他的字节反转方法。这只是我的个人意见。


我认为这是最好的答案。 - undefined

32

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


1
这里可能是最好的答案,避免在标准解决方案已经存在时重新发明轮子。 - legends2k
2
这在大端机器上不起作用:https://dev59.com/nXvaa4cB1Zd3GeqPF6jV - CurtisJC
请注意,htonl()不在标准C库中,需要重新实现。一些实现返回uint32_t,而其他实现返回unsigned longhton32()可以解决这个问题。 - chux - Reinstate Monica

10

"我交换每个字节对吗?" -> 是的,为了在小端和大端之间转换,只需要给字节相反的顺序。

但首先要注意以下几点:

  • uint32_t 的大小为 32 位,即 4 字节,即 8 个十六进制数字
  • 掩码 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

例如,对于“x=0xabcdef12”无法工作。或者更准确地说,由于您使用了有符号的int,因此对于负数,“x>>24”不会用0填充前24位,而是用符号位填充。这可能是1。因此,要么像其他术语一样也掩码“x>>24”(“(x>>24)&0xff”),要么只需像其他人一样使用“uint32_t”。 - chrslg

4

有一种稍微不同的方法可以解决这个问题,有时候会很有用,那就是将十六位或三十二位值与字符数组联合起来。当我接收到大端序列的串行消息,但又在使用小端微控制器时,我刚好在做这个。

union MessageLengthUnion
{

    uint16_t asInt;
    uint8_t asChars[2];

};

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

如果您要存储32位值,则可以使用长度为4的数组。


3

再提一个建议:

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

能否解释一下它的工作原理以及这种方法的好处呢? - SamB
1
第一条指令交换了16位字(即单词长度),第二条指令交换了8位字节(即字符长度),从而实现了大端到小端以及小端到大端的转换。输出结果为:23EFCDAB。另外,好处在于不需要额外的变量和步骤较少。 - Saurabh Sengar

3
我假设你正在使用Linux操作系统。
包含头文件"byteswap.h",并使用int32_t bswap_32(int32_t argument); 这是一个逻辑视图,在实际中,可以在/usr/include/byteswap.h中看到。

2

一个简单的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;
}

2

以下是OP的错误代码:

  • 交换操作是在4位(nibble)边界上执行的,而不是8位(byte)边界上执行的。
  • 最后四个交换的左移<<操作是错误的,它们应该是右移>>操作,并且它们的移位值也需要进行更正。
  • 使用中间存储是不必要的,因此可以重写代码以使其更简洁/易识别。这样做,一些编译器将能够通过识别经常使用的模式来更好地优化代码。

请考虑以下有效转换无符号值的代码:

// 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寄存器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接