如何在c++中转换大端值和小端值?
为了清晰起见,我必须将二进制数据(双精度浮点值以及32位和64位整数)从一个CPU架构转换到另一个CPU架构。这并不涉及网络,因此ntoh()和类似的函数在这里不能工作。
注意:我接受的答案直接适用于我的目标编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更方便的答案。
如何在c++中转换大端值和小端值?
为了清晰起见,我必须将二进制数据(双精度浮点值以及32位和64位整数)从一个CPU架构转换到另一个CPU架构。这并不涉及网络,因此ntoh()和类似的函数在这里不能工作。
注意:我接受的答案直接适用于我的目标编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更方便的答案。
当前回答
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);
}
似乎安全的方法是在每个单词上使用“顿音”。所以,如果你有。
std::vector<uint16_t> storage(n); // where n is the number to be converted
// the following would do the trick
std::transform(word_storage.cbegin(), word_storage.cend()
, word_storage.begin(), [](const uint16_t input)->uint16_t {
return htons(input); });
如果您是在一个大端系统上,那么上面的代码将是一个无操作,因此我将查找您的平台使用的任何编译时条件,以确定htons是否是一个无操作。毕竟是O(n)在Mac上,它会是这样的……
#if (__DARWIN_BYTE_ORDER != __DARWIN_BIG_ENDIAN)
std::transform(word_storage.cbegin(), word_storage.cend()
, word_storage.begin(), [](const uint16_t input)->uint16_t {
return htons(input); });
#endif
简单地说:
#include <climits>
template <typename T>
T swap_endian(T u)
{
static_assert (CHAR_BIT == 8, "CHAR_BIT != 8");
union
{
T u;
unsigned char u8[sizeof(T)];
} source, dest;
source.u = u;
for (size_t k = 0; k < sizeof(T); k++)
dest.u8[k] = source.u8[sizeof(T) - k - 1];
return dest.u;
}
用法:swap_endian < uint32_t >(42)。
如果您采用反转单词中位序的常见模式,并剔除每个字节中反转位的部分,那么您将只剩下反转单词中的字节的部分。对于64位:
x = ((x & 0x00000000ffffffff) << 32) ^ ((x >> 32) & 0x00000000ffffffff);
x = ((x & 0x0000ffff0000ffff) << 16) ^ ((x >> 16) & 0x0000ffff0000ffff);
x = ((x & 0x00ff00ff00ff00ff) << 8) ^ ((x >> 8) & 0x00ff00ff00ff00ff);
编译器应该清除多余的位屏蔽操作(我把它们留在了突出显示模式),但如果它没有,你可以这样重写第一行:
x = ( x << 32) ^ (x >> 32);
在大多数架构上,这通常应该简化为一条旋转指令(忽略整个操作可能是一条指令)。
在RISC处理器上,大而复杂的常量可能会导致编译困难。不过,您可以简单地计算前一个的每个常数。像这样:
uint64_t k = 0x00000000ffffffff; /* compiler should know a trick for this */
x = ((x & k) << 32) ^ ((x >> 32) & k);
k ^= k << 16;
x = ((x & k) << 16) ^ ((x >> 16) & k);
k ^= k << 8;
x = ((x & k) << 8) ^ ((x >> 8) & k);
如果你愿意,你可以把它写成一个循环。这样做效率不高,只是为了好玩:
int i = sizeof(x) * CHAR_BIT / 2;
uintmax_t k = (1 << i) - 1;
while (i >= 8)
{
x = ((x & k) << i) ^ ((x >> i) & k);
i >>= 1;
k ^= k << i;
}
为了完整起见,这里是第一种形式的简化32位版本:
x = ( x << 16) ^ (x >> 16);
x = ((x & 0x00ff00ff) << 8) ^ ((x >> 8) & 0x00ff00ff);
我只是想在这里添加我自己的解,因为我在任何地方都没有看到它。它是一个小而可移植的c++模板函数,并且只使用比特操作。
template<typename T> inline static T swapByteOrder(const T& val) {
int totalBytes = sizeof(val);
T swapped = (T) 0;
for (int i = 0; i < totalBytes; ++i) {
swapped |= (val >> (8*(totalBytes-i-1)) & 0xFF) << (8*i);
}
return swapped;
}