如何在c++中转换大端值和小端值?
为了清晰起见,我必须将二进制数据(双精度浮点值以及32位和64位整数)从一个CPU架构转换到另一个CPU架构。这并不涉及网络,因此ntoh()和类似的函数在这里不能工作。
注意:我接受的答案直接适用于我的目标编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更方便的答案。
如何在c++中转换大端值和小端值?
为了清晰起见,我必须将二进制数据(双精度浮点值以及32位和64位整数)从一个CPU架构转换到另一个CPU架构。这并不涉及网络,因此ntoh()和类似的函数在这里不能工作。
注意:我接受的答案直接适用于我的目标编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更方便的答案。
当前回答
请注意,至少对于Windows, htonl()比它们的内在对应_byteswap_ulong()慢得多。前者是对ws2_32.dll的一个DLL库调用,后者是一条BSWAP汇编指令。因此,如果你正在编写一些依赖于平台的代码,为了提高速度,最好使用intrinsic:
#define htonl(x) _byteswap_ulong(x)
这对于。png图像处理尤其重要,其中所有整数都保存在大端格式中,并说明“One can use htonl()…”{用来降低典型Windows程序的速度,如果你没有准备好}。
其他回答
c++20无分支版本,现在std::endian已经存在,但在c++23之前增加了std::byteswap
#include <bit>
#include <type_traits>
#include <concepts>
#include <array>
#include <cstring>
#include <iostream>
#include <bitset>
template <int LEN, int OFF=LEN/2>
class do_swap
{
// FOR 8 bytes:
// LEN=8 (LEN/2==4) <H><G><F><E><D><C><B><A>
// OFF=4: FROM=0, TO=7 => [A]<G><F><E><D><C><B>[H]
// OFF=3: FROM=1, TO=6 => [A][B]<F><E><D><C>[G][H]
// OFF=2: FROM=2, TO=5 => [A][B][C]<E><D>[F][G][H]
// OFF=1: FROM=3, TO=4 => [A][B][C][D][E][F][G][H]
// OFF=0: FROM=4, TO=3 => DONE
public:
enum consts {FROM=LEN/2-OFF, TO=(LEN-1)-FROM};
using NXT=do_swap<LEN, OFF-1>;
// flip the first and last for the current iteration's range
static void flip(std::array<std::byte, LEN>& b)
{
std::byte tmp=b[FROM];
b[FROM]=b[TO];
b[TO]=tmp;
NXT::flip(b);
}
};
template <int LEN>
class do_swap<LEN, 0> // STOP the template recursion
{
public:
static void flip(std::array<std::byte, LEN>&)
{
}
};
template<std::integral T, std::endian TO, std::endian FROM=std::endian::native>
requires ((TO==std::endian::big) || (TO==std::endian::little))
&& ((FROM==std::endian::big) || (FROM==std::endian::little))
class endian_swap
{
public:
enum consts {BYTE_COUNT=sizeof(T)};
static T cvt(const T integral)
{
// if FROM and TO are the same -- nothing to do
if (TO==FROM)
{
return integral;
}
// endian::big --> endian::little is the same as endian::little --> endian::big
// the bytes have to be reversed
// memcpy seems to be the most supported way to do byte swaps in a defined way
std::array<std::byte, BYTE_COUNT> bytes;
std::memcpy(&bytes, &integral, BYTE_COUNT);
do_swap<BYTE_COUNT>::flip(bytes);
T ret;
std::memcpy(&ret, &bytes, BYTE_COUNT);
return ret;
}
};
std::endian big()
{
return std::endian::big;
}
std::endian little()
{
return std::endian::little;
}
std::endian native()
{
return std::endian::native;
}
long long swap_to_big(long long x)
{
return endian_swap<long long, std::endian::big>::cvt(x);
}
long long swap_to_little(long long x)
{
return endian_swap<long long, std::endian::little>::cvt(x);
}
void show(std::string label, long long x)
{
std::cout << label << "\t: " << std::bitset<64>(x) << " (" << x << ")" << std::endl;
}
int main(int argv, char ** argc)
{
long long init=0xF8FCFEFF7F3F1F0;
long long to_big=swap_to_big(init);
long long to_little=swap_to_little(init);
show("Init", init);
show(">big", to_big);
show(">little", to_little);
}
从大端序到小端序的过程与从小端序到大端序的过程是一样的。
下面是一些示例代码:
void swapByteOrder(unsigned short& us)
{
us = (us >> 8) |
(us << 8);
}
void swapByteOrder(unsigned int& ui)
{
ui = (ui >> 24) |
((ui<<8) & 0x00FF0000) |
((ui>>8) & 0x0000FF00) |
(ui << 24);
}
void swapByteOrder(unsigned long long& ull)
{
ull = (ull >> 56) |
((ull<<40) & 0x00FF000000000000) |
((ull<<24) & 0x0000FF0000000000) |
((ull<<8) & 0x000000FF00000000) |
((ull>>8) & 0x00000000FF000000) |
((ull>>24) & 0x0000000000FF0000) |
((ull>>40) & 0x000000000000FF00) |
(ull << 56);
}
我最近写了一个宏来在C中实现这个功能,但它在c++中同样有效:
#define REVERSE_BYTES(...) do for(size_t REVERSE_BYTES=0; REVERSE_BYTES<sizeof(__VA_ARGS__)>>1; ++REVERSE_BYTES)\
((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES],\
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES],\
((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES];\
while(0)
它接受任何类型,并反转传入参数中的字节。 示例用法:
int main(){
unsigned long long x = 0xABCDEF0123456789;
printf("Before: %llX\n",x);
REVERSE_BYTES(x);
printf("After : %llX\n",x);
char c[7]="nametag";
printf("Before: %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
REVERSE_BYTES(c);
printf("After : %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
}
打印:
Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman
上面的内容是完全可以复制/粘贴的,但这里有很多内容,所以我将逐条分解它的工作原理:
第一件值得注意的事情是整个宏被封装在一个do while(0)块中。这是一种常见的习惯用法,允许在宏后面使用正常的分号。
接下来是使用名为REVERSE_BYTES的变量作为for循环的计数器。宏本身的名称用作变量名,以确保它不会与范围内的任何其他符号冲突。由于该名称是在宏的展开中使用的,因此在这里作为变量名使用时不会再次展开。
在for循环中,有两个字节被引用并交换了XOR(因此不需要临时变量名):
((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES]
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES]
__VA_ARGS__表示给宏的任何内容,并用于增加可能传入内容的灵活性(尽管不是很多)。然后,该参数的地址被转换为unsigned char指针,以允许通过数组[]下标交换其字节。
最后一个特殊之处是缺少{}大括号。它们不是必需的,因为每次交换中的所有步骤都使用逗号操作符连接,使它们成为一条语句。
最后,值得注意的是,如果速度是最优先考虑的,这不是理想的方法。如果这是一个重要因素,那么其他答案中引用的一些特定于类型的宏或特定于平台的指令可能是更好的选择。然而,这种方法可以移植到所有类型、所有主要平台以及C和c++语言。
查找位移位,因为这基本上是所有你需要做的交换从小->大端dian。然后根据位的大小,改变位移位的方式。
使用下面的代码,您可以轻松地在BigEndian和LittleEndian之间进行切换
#define uint32_t unsigned
#define uint16_t unsigned short
#define swap16(x) ((((uint16_t)(x) & 0x00ff)<<8)| \
(((uint16_t)(x) & 0xff00)>>8))
#define swap32(x) ((((uint32_t)(x) & 0x000000ff)<<24)| \
(((uint32_t)(x) & 0x0000ff00)<<8)| \
(((uint32_t)(x) & 0x00ff0000)>>8)| \
(((uint32_t)(x) & 0xff000000)>>24))