For reversing byte order in variables and compile-time constants:
According to godbolt.org, __builtin_bswap16(), __builtin_bswap32(), and __builtin_bswap64() are supported by GCC, Clang, and ICC.
I would prefer to use those over open-coded versions, because the compiler is more likely to optimize these to optimal machine code. Simply put, something like
#include <stdint.h>
// When neither or both of REV_BUILTINS and REV_OPENCODED are defined, autodetect based on compiler major version.
#if (!defined(REV_BUILTINS) && !defined(REV_OPENCODED)) || (defined(REV_BUILTINS) && defined(REV_OPENCODED))
#undef REV_BUILTINS
#undef REV_OPENCODED
// Built-in support since GCC 5.x.x
#if defined(__GNUC__) && __GNUC__ >= 5
#define REV_BUILTINS
#undef REV_OPENCODED
// Built-in support since ICC 13.x.x
#elif defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1300
#define REV_BUILTINS
#undef REV_OPENCODED
// Built-in support since clang 10.x.x
#elif defined(__clang__) && __clang_major__ >= 10
#define REV_BUILTINS
#undef REV_OPENCODED
// No built-in support
#else
#undef REV_BUILTINS
#define REV_OPENCODED
#endif
// Implementation of the rev16(), rev32(), and rev64() functions.
// These reverse the byte order (between little-endian and big-endian).
#if defined(REV_BUILTINS)
// Use compiler-provided __builtin_bswapN() built-in functions
#define rev16(v) __builtin_bswap16(v)
#define rev32(v) __builtin_bswap32(v)
#define rev64(v) __builtin_bswap64(v)
#elif defined(REV_OPENCODED)
// Use open-coded byte order reversing functions
static inline uint_fast16_t rev16(uint_fast16_t v) {
return ((v >> 8) & 0xFF) | ((v & 0xFF) << 8);
}
static inline uint32_t rev32(uint32_t v) {
v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8);
v = ((v >> 16) & 0x0000FFFF) | ((v & 0x0000FFFF) << 16);
return v;
}
static inline uint64_t rev64(uint64_t v) {
v = ((v >> 8) & UINT64_C(0x00FF00FF00FF00FF)) | ((v & UINT64_C(0x00FF00FF00FF00FF)) << 8);
v = ((v >> 16) & UINT64_C(0x0000FFFF0000FFFF)) | ((v & UINT64_C(0x0000FFFF0000FFFF)) << 16);
v = ((v >> 32) & UINT64_C(0x00000000FFFFFFFF)) | ((v & UINT64_C(0x00000000FFFFFFFF)) << 32);
return v;
}
#else
#error BUG: Neither REV_BUILTINS nor REV_OPENCODED got defined.
#endif
When neither or both of REV_BUILTINS and REV_OPENCODED are defined, the built-ins are used on GCC 5.x.x and later, ICC 13.x.x and later, and clang 10.x.x and later, and the open-coded ones otherwise. Otherwise, the defined one determines which are used. The BUG: error should never occur, but is there in case a later edit causes some arch to be missed.
In all cases, when compilation succeeds, you get rev16(), rev32(), and rev64(), which reverse the byte order in their argument. For signed arguments, you'll want to cast the return value to the appropriate signed type (intN_t or int_fastN_t).
For code that wants or needs to support random byte orders in e.g. binary files, I do like to implement functions that take a bit pattern of \$k = \lfloor\log_2(N/8)\rfloor\$ bits for revN, each bit enabling or disabling the operation on each line in the open-coded variants. Then, each byte order (for specific size of value) is represented by a different \$k\$-bit value: 1 bit for 16-bit values; 2 bits for 32-bit values; 3 bits for 64-bit values; 4 bits for 128-bit values; and so on. I have a prototype value for each type in a file header, and test all possible byte orders in a loop until the prototype value is parsed as the expected logical value, so they also act as file format identifiers. It does incur a small penalty for multi-byte fields, but with current processors and microcontrollers, that overhead tends to be irrelevant compared to the storage/communications I/O speeds.