将Little Endian转换为Big Endian
我只是想问一下我的方法是否正确,从小端到大端转换,只是为了确保我理解差异。
我有一个存储在little-endian中的数字,这里是数字的二进制和hex表示:
0001 0010 0011 0100 0101 0110 0111 1000 12345678
在big-endian格式中,我相信应该交换字节,如下所示:
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);
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添加了一条改变事情的评论。
“由hex表示(st uv wx yz)表示的32位数值应记录在四字节字段中(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.73
在移位常数(右操作数)之后使用u
导致与没有它的情况相同。
b3 = (num & 0xff000000) >> 24u; b[3] = (uint8_t) (num >> 24u); // same as b3 = (num & 0xff000000) >> 24; b[3] = (uint8_t) (num >> 24);
我认为你可以使用函数htonl()
。 网络字节顺序是大端。
“我把每个字节换成了正确的吗?” – >是的,要在little和big endian之间进行转换,你只需给出相反顺序的字节。 但最初意识到很少:
-
uint32_t
大小是32位,即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 ;
你可以这样做:
int x = 0x12345678; x = ( x >> 24 ) | (( x << 8) & 0x00ff0000 )| ((x >> 8) & 0x0000ff00) | ( x << 24) ; printf("value = %x", x); // x will be printed as 0x78563412
对不起,我的回答有点太晚,但似乎没有人提到内置函数来反转字节顺序,这在性能方面非常重要 。
大多数现代处理器都是little-endian,而所有网络协议都是big-endian。 这是历史,你可以在维基百科上找到更多。 但这意味着我们的处理器在浏览互联网时会在小端和大端之间转换数百万次。
这就是为什么大多数架构都有专门的处理器指令来促进这项任务。 对于x86架构,有BSWAP
指令,对于ARM,有REV
。 这是反转字节顺序的最有效方法 。
为了避免在我们的C代码中进行汇编,我们可以使用内置函数。 对于GCC,有__builtin_bswap32()
函数,对于Visual C ++,有_byteswap_ulong()
。 这些函数将在大多数体系结构上生成一个处理器指令 。
这是一个例子:
#include #include 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美分。
解决这个问题的一种稍微不同的方法有时可能是有用的是具有十六或三十二位值和一组字符的并集。 我刚刚收到大端序的串行消息,但我正在处理一个小端微处理器。
union MessageLengthUnion {
uint16_t asInt; uint8_t asChars[2];
};
然后,当我收到消息时,我将第一个接收到的uint8放在.asChars [1]中,第二个放在.asChars [0]中,然后我将其作为我的程序其余部分中的.asInt部分进行访问。 如果你有一个32位的值存储,你可以让数组长四。
还有一个建议:
unsigned int a = 0xABCDEF23; a = ((a&(0x0000FFFF)) << 16) | ((a&(0xFFFF0000)) >> 16); a = ((a&(0x00FF00FF)) << 8) | ((a&(0xFF00FF00)) >>8); printf("%0x\n",a);
我假设你在linux上
包含"byteswap.h"
和使用int32_t bswap_32(int32_t argument);
这是逻辑视图,实际上参见/usr/include/byteswap.h
OP的代码不正确,原因如下:
- 交换是在半字节(4位)边界上执行的,而不是字节(8位)边界。
- 左移的
<<
最后四次交换的操作是不正确的,它们应该是右移>>
操作,它们的移位值也需要纠正。 - 不需要使用中间存储,因此可以重写代码以使其更简洁/可识别。 通过这样做,一些编译器将能够通过识别经常使用的模式来更好地优化代码。
请考虑以下代码,它可以有效地转换无符号值:
// Swap endian (big to little) or (little to big) uint32_t num = 0x12345678; uint32_t res = ((num & 0x000000FF) << 16) | ((num & 0x0000FF00) << 8) | ((num & 0x00FF0000) >> 8) | ((num & 0xFF000000) >> 16); printf("%0x\n", res);
这里以二进制和hex表示结果,注意字节是如何交换的:
0111 1000 0101 0110 0011 0100 0001 0010 78563412
优化
在性能方面,请将其留给编译器以尽可能优化代码。 对于像这样的简单算法,你应该避免像数组这样的不必要的数据结构,这样做通常会导致不同的指令行为,例如访问RAM而不是使用CPU寄存器。
您可以使用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);
一个简单的C程序,可以从小到大转换
#include 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; }