This question is about swapping bytes of an uint64_t
value, or in other words, about converting between endianness of an integer value:
#include <algorithm>
#include <cstdint>
#include <iomanip>
#include <iostream>
uint64_t& swapBytes1(uint64_t& num) {
using std::swap;
union {
uint8_t bytes[8];
uint64_t num;
} transformer;
transformer.num = num;
swap(transformer.bytes[0], transformer.bytes[7]);
swap(transformer.bytes[1], transformer.bytes[6]);
swap(transformer.bytes[2], transformer.bytes[5]);
swap(transformer.bytes[3], transformer.bytes[4]);
return num = transformer.num;
}
uint64_t& swapBytes2(uint64_t& num) {
using std::swap;
union {
struct {
uint8_t b0;
uint8_t b1;
uint8_t b2;
uint8_t b3;
uint8_t b4;
uint8_t b5;
uint8_t b6;
uint8_t b7;
} bytes;
uint64_t num;
} transformer;
transformer.num = num;
swap(transformer.bytes.b0, transformer.bytes.b7);
swap(transformer.bytes.b1, transformer.bytes.b6);
swap(transformer.bytes.b2, transformer.bytes.b5);
swap(transformer.bytes.b3, transformer.bytes.b4);
return num = transformer.num;
}
void main() {
uint64_t num = 0x0123456789abcdef;
std::cout << std::hex << num << "\n";
std::cout << swapBytes1(num) << "\n";
swapBytes2(num);
std::cout << num << "\n";
}
Critique request
As I am not a professional C++ developer, I would like to receive any comment/answer that could improve my C++ coding habits.
1 Answer 1
Technically this is UB.
Writing a value to one member of a union, then reading from another member (that was not written to) of the union is undefined behavior.
You would be better off just casting to a char*
and just reversing it.
swap(std::uint64_t& num)
{
// I like the use of reinterpret_cast here
// it makes sure that somebody reading the code notices
// and takes special care to review what is happening.
//
char* first = reinterpret_cast<char*>(&num);
char* last = first + sizeof(num);
// The advantage of reverse here:
//
// The standard library can have pre built optimizations
// that will use hardware specific optimizations of this that
// can convert it to a single instruction on some hardware.
//
std::reverse(first, last);
}
The question becomes are you doing this to support endianness?
If so then your code is only going to work on machines that have the same endianness as the platform you are currently working on.
Normally people want to swap from platform byte order to network byte order (or the reverse). Depending on architecture this is either a swap (as you have above) or a NO-OP or for some weird hardware something completely different.
To support this we have specific family of functions:
htonl() // host to network long (32 byte)
htons() // host to network short (16 byte)
ntohl() // network to host long (32 byte)
ntohs() // network to host short (16 byte)
These functions will do the appropriate conversion for your hardware.
-
\$\begingroup\$
htonl()
only does 32 bits,htons()
16 bits. There might be non-standard functions to handle 64 bits at a time, likebswap_64()
in glibc, or builtins like__builtin_bswap64()
in GCC. \$\endgroup\$G. Sliepen– G. Sliepen2020年12月11日 19:59:58 +00:00Commented Dec 11, 2020 at 19:59 -
\$\begingroup\$ @G.Sliepen Anyway, those functions solve a different problem, that on little-endian platforms reduces to this. On big-endian platforms, they are identity-functions. \$\endgroup\$Deduplicator– Deduplicator2020年12月12日 01:23:35 +00:00Commented Dec 12, 2020 at 1:23
Explore related questions
See similar questions with these tags.
std::reverse
for the 1st version? \$\endgroup\$htonl()
and family. linux.die.net/man/3/htonl \$\endgroup\$