如何在c++中转换大端值和小端值?
为了清晰起见,我必须将二进制数据(双精度浮点值以及32位和64位整数)从一个CPU架构转换到另一个CPU架构。这并不涉及网络,因此ntoh()和类似的函数在这里不能工作。
注意:我接受的答案直接适用于我的目标编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更方便的答案。
如何在c++中转换大端值和小端值?
为了清晰起见,我必须将二进制数据(双精度浮点值以及32位和64位整数)从一个CPU架构转换到另一个CPU架构。这并不涉及网络,因此ntoh()和类似的函数在这里不能工作。
注意:我接受的答案直接适用于我的目标编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更方便的答案。
当前回答
我最近写了一个宏来在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++语言。
其他回答
似乎安全的方法是在每个单词上使用“顿音”。所以,如果你有。
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
下面介绍如何读取以IEEE 754 64位格式存储的double,即使您的主机使用不同的系统。
/*
* read a double from a stream in ieee754 format regardless of host
* encoding.
* fp - the stream
* bigendian - set to if big bytes first, clear for little bytes
* first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
unsigned char buff[8];
int i;
double fnorm = 0.0;
unsigned char temp;
int sign;
int exponent;
double bitval;
int maski, mask;
int expbits = 11;
int significandbits = 52;
int shift;
double answer;
/* read the data */
for (i = 0; i < 8; i++)
buff[i] = fgetc(fp);
/* just reverse if not big-endian*/
if (!bigendian)
{
for (i = 0; i < 4; i++)
{
temp = buff[i];
buff[i] = buff[8 - i - 1];
buff[8 - i - 1] = temp;
}
}
sign = buff[0] & 0x80 ? -1 : 1;
/* exponet in raw format*/
exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);
/* read inthe mantissa. Top bit is 0.5, the successive bits half*/
bitval = 0.5;
maski = 1;
mask = 0x08;
for (i = 0; i < significandbits; i++)
{
if (buff[maski] & mask)
fnorm += bitval;
bitval /= 2.0;
mask >>= 1;
if (mask == 0)
{
mask = 0x80;
maski++;
}
}
/* handle zero specially */
if (exponent == 0 && fnorm == 0)
return 0.0;
shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
/* nans have exp 1024 and non-zero mantissa */
if (shift == 1024 && fnorm != 0)
return sqrt(-1.0);
/*infinity*/
if (shift == 1024 && fnorm == 0)
{
#ifdef INFINITY
return sign == 1 ? INFINITY : -INFINITY;
#endif
return (sign * 1.0) / 0.0;
}
if (shift > -1023)
{
answer = ldexp(fnorm + 1.0, shift);
return answer * sign;
}
else
{
/* denormalised numbers */
if (fnorm == 0.0)
return 0.0;
shift = -1022;
while (fnorm < 1.0)
{
fnorm *= 2;
shift--;
}
answer = ldexp(fnorm, shift);
return answer * sign;
}
}
对于这套函数的其余部分,包括写和整数例程,请参阅我的github项目
https://github.com/MalcolmMcLean/ieee754
在大多数POSIX系统中(虽然不是在POSIX标准中)有end .h,它可以用来确定系统使用的编码。然后是这样的:
unsigned int change_endian(unsigned int x)
{
unsigned char *ptr = (unsigned char *)&x;
return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
}
这将交换顺序(从大端序到小端序):
如果你有数字0xDEADBEEF(在一个小端序系统中存储为0xEFBEADDE), ptr[0]将是0xEF, ptr[1]是0xBE,等等。
但是如果你想将它用于网络,那么htons, htonl和htonll(以及它们的逆ntohs, ntohl和ntohll)将有助于从主机顺序转换到网络顺序。
认真……我不明白为什么所有的解决方案都那么复杂!最简单、最通用的模板函数如何?它可以在任何操作系统的任何情况下交换任何大小的任何类型????
template <typename T>
void SwapEnd(T& var)
{
static_assert(std::is_pod<T>::value, "Type must be POD type for safety");
std::array<char, sizeof(T)> varArray;
std::memcpy(varArray.data(), &var, sizeof(T));
for(int i = 0; i < static_cast<int>(sizeof(var)/2); i++)
std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
std::memcpy(&var, varArray.data(), sizeof(T));
}
这是C和c++结合的神奇力量!只需逐个字符交换原始变量。
要点1:没有操作符:请记住,我没有使用简单的赋值操作符“=”,因为当反转字节序时,一些对象将被打乱,复制构造函数(或赋值操作符)将不起作用。因此,一个字符一个字符地复制它们更加可靠。
Point 2: Be aware of alignment issues: Notice that we're copying to and from an array, which is the right thing to do because the C++ compiler doesn't guarantee that we can access unaligned memory (this answer was updated from its original form for this). For example, if you allocate uint64_t, your compiler cannot guarantee that you can access the 3rd byte of that as a uint8_t. Therefore, the right thing to do is to copy this to a char array, swap it, then copy it back (so no reinterpret_cast). Notice that compilers are mostly smart enough to convert what you did back to a reinterpret_cast if they're capable of accessing individual bytes regardless of alignment.
使用此函数:
double x = 5;
SwapEnd(x);
现在x的字节序不同了。
在模板函数中围绕枢轴使用老式的3-step-xor技巧进行字节交换,提供了一个灵活、快速的O(ln2)解决方案,不需要库,这里的风格也拒绝1字节类型:
template<typename T>void swap(T &t){
for(uint8_t pivot = 0; pivot < sizeof(t)/2; pivot ++){
*((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
*((uint8_t *)&t+sizeof(t)-1- pivot) ^= *((uint8_t *)&t + pivot);
*((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
}
}