|
| 1 | +// Companion source for docs/big-endian_little-endian.md |
| 2 | +// |
| 3 | +// Demonstrates byte-order techniques referenced in the doc: |
| 4 | +// 1. Runtime endian detection (the classic pointer-cast trick). |
| 5 | +// 2. Compile-time endian detection via std::endian (C++20). |
| 6 | +// 3. Printing raw memory bytes vs. logical big-endian bytes. |
| 7 | +// 4. Manual byte swapping for 16/32/64-bit words. |
| 8 | +// 5. std::byteswap (C++23) when available, else a manual fallback. |
| 9 | +// 6. Robotics-flavored examples: CAN frame ID + payload, network |
| 10 | +// protocol header on the wire, and an embedded sensor word. |
| 11 | +// |
| 12 | +// Build target: big_endian_little_endian (C++20). |
| 13 | + |
| 14 | +#include <array> |
| 15 | +#include <bit> // std::endian, std::bit_cast |
| 16 | +#include <cstdint> |
| 17 | +#include <cstring> // std::memcpy |
| 18 | +#include <iomanip> |
| 19 | +#include <iostream> |
| 20 | +#include <span> |
| 21 | +#include <string_view> |
| 22 | + |
| 23 | +#include <version> // feature-test macros (__cpp_lib_byteswap) |
| 24 | + |
| 25 | +// --- Helpers ----------------------------------------------------------------- |
| 26 | + |
| 27 | +// Print bytes of any trivially-copyable value as stored in memory. |
| 28 | +template <class T> |
| 29 | +void print_memory_bytes(const T& value, std::string_view label) { |
| 30 | + const auto* p = reinterpret_cast<const unsigned char*>(&value); |
| 31 | + std::cout << label << " (in-memory): "; |
| 32 | + std::cout << std::hex << std::setfill('0'); |
| 33 | + for (std::size_t i = 0; i < sizeof(T); ++i) { |
| 34 | + std::cout << std::setw(2) << static_cast<int>(p[i]) << ' '; |
| 35 | + } |
| 36 | + std::cout << std::dec << std::setfill(' ') << '\n'; |
| 37 | +} |
| 38 | + |
| 39 | +// Print bytes from MSB to LSB (logical big-endian view) regardless of host. |
| 40 | +template <class T> |
| 41 | +void print_logical_be(const T& value, std::string_view label) { |
| 42 | + const auto* p = reinterpret_cast<const unsigned char*>(&value); |
| 43 | + std::cout << label << " (logical BE): "; |
| 44 | + std::cout << std::hex << std::setfill('0'); |
| 45 | + if constexpr (std::endian::native == std::endian::little) { |
| 46 | + for (std::size_t i = sizeof(T); i-- > 0;) { |
| 47 | + std::cout << std::setw(2) << static_cast<int>(p[i]) << ' '; |
| 48 | + } |
| 49 | + } else { |
| 50 | + for (std::size_t i = 0; i < sizeof(T); ++i) { |
| 51 | + std::cout << std::setw(2) << static_cast<int>(p[i]) << ' '; |
| 52 | + } |
| 53 | + } |
| 54 | + std::cout << std::dec << std::setfill(' ') << '\n'; |
| 55 | +} |
| 56 | + |
| 57 | +// Classic runtime check (mirrors the doc's isBigEndian()). |
| 58 | +bool runtime_is_big_endian() { |
| 59 | + std::uint32_t test = 1; |
| 60 | + const auto* byte = reinterpret_cast<const unsigned char*>(&test); |
| 61 | + return byte[0] == 0; |
| 62 | +} |
| 63 | + |
| 64 | +// Portable byte swaps. Used as a fallback when std::byteswap is unavailable. |
| 65 | +constexpr std::uint16_t bswap16(std::uint16_t v) noexcept { |
| 66 | + return static_cast<std::uint16_t>((v << 8) | (v >> 8)); |
| 67 | +} |
| 68 | +constexpr std::uint32_t bswap32(std::uint32_t v) noexcept { |
| 69 | + return ((v & 0x000000FFu) << 24) | |
| 70 | + ((v & 0x0000FF00u) << 8) | |
| 71 | + ((v & 0x00FF0000u) >> 8) | |
| 72 | + ((v & 0xFF000000u) >> 24); |
| 73 | +} |
| 74 | +constexpr std::uint64_t bswap64(std::uint64_t v) noexcept { |
| 75 | + return ((v & 0x00000000000000FFull) << 56) | |
| 76 | + ((v & 0x000000000000FF00ull) << 40) | |
| 77 | + ((v & 0x0000000000FF0000ull) << 24) | |
| 78 | + ((v & 0x00000000FF000000ull) << 8) | |
| 79 | + ((v & 0x000000FF00000000ull) >> 8) | |
| 80 | + ((v & 0x0000FF0000000000ull) >> 24) | |
| 81 | + ((v & 0x00FF000000000000ull) >> 40) | |
| 82 | + ((v & 0xFF00000000000000ull) >> 56); |
| 83 | +} |
| 84 | + |
| 85 | +// host <-> big-endian conversion (no-op on big-endian hosts). |
| 86 | +template <class T> |
| 87 | +constexpr T host_to_be(T v) noexcept { |
| 88 | + static_assert(std::is_integral_v<T>, "integral only"); |
| 89 | + if constexpr (std::endian::native == std::endian::big) { |
| 90 | + return v; |
| 91 | + } else { |
| 92 | + if constexpr (sizeof(T) == 1) return v; |
| 93 | + else if constexpr (sizeof(T) == 2) return static_cast<T>(bswap16(static_cast<std::uint16_t>(v))); |
| 94 | + else if constexpr (sizeof(T) == 4) return static_cast<T>(bswap32(static_cast<std::uint32_t>(v))); |
| 95 | + else if constexpr (sizeof(T) == 8) return static_cast<T>(bswap64(static_cast<std::uint64_t>(v))); |
| 96 | + else static_assert(sizeof(T) == 0, "unsupported size"); |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +template <class T> |
| 101 | +constexpr T be_to_host(T v) noexcept { return host_to_be(v); } // symmetric |
| 102 | + |
| 103 | +void banner(std::string_view title) { |
| 104 | + std::cout << "\n=== " << title << " ===\n"; |
| 105 | +} |
| 106 | + |
| 107 | +// --- Demos ------------------------------------------------------------------- |
| 108 | + |
| 109 | +void demo_runtime_detection() { |
| 110 | + banner("1. Runtime endian detection (doc's classic trick)"); |
| 111 | + std::cout << "runtime_is_big_endian(): " |
| 112 | + << (runtime_is_big_endian() ? "Big-endian" : "Little-endian") << '\n'; |
| 113 | +} |
| 114 | + |
| 115 | +void demo_compile_time_detection() { |
| 116 | + banner("2. Compile-time detection via std::endian (C++20)"); |
| 117 | + if constexpr (std::endian::native == std::endian::little) { |
| 118 | + std::cout << "std::endian::native == little\n"; |
| 119 | + } else if constexpr (std::endian::native == std::endian::big) { |
| 120 | + std::cout << "std::endian::native == big\n"; |
| 121 | + } else { |
| 122 | + std::cout << "std::endian::native == mixed/unknown\n"; |
| 123 | + } |
| 124 | +} |
| 125 | + |
| 126 | +void demo_byte_layout() { |
| 127 | + banner("3. Byte layout of 0x12345678 (doc's printBytes/printBigEndianBytes)"); |
| 128 | + std::uint32_t number = 0x12345678u; |
| 129 | + std::cout << "value = 0x" << std::hex << number << std::dec << '\n'; |
| 130 | + print_memory_bytes(number, " number"); |
| 131 | + print_logical_be (number, " number"); |
| 132 | +} |
| 133 | + |
| 134 | +void demo_byteswap() { |
| 135 | + banner("4. Byte swapping (manual + std::byteswap if available)"); |
| 136 | + std::uint32_t v32 = 0xDEADBEEFu; |
| 137 | + std::uint16_t v16 = 0xABCDu; |
| 138 | + std::uint64_t v64 = 0x0123456789ABCDEFull; |
| 139 | + |
| 140 | + std::cout << std::hex << std::setfill('0'); |
| 141 | + std::cout << "manual bswap16(0x" << std::setw(4) << v16 << ") = 0x" |
| 142 | + << std::setw(4) << bswap16(v16) << '\n'; |
| 143 | + std::cout << "manual bswap32(0x" << std::setw(8) << v32 << ") = 0x" |
| 144 | + << std::setw(8) << bswap32(v32) << '\n'; |
| 145 | + std::cout << "manual bswap64(0x" << std::setw(16) << v64 << ") = 0x" |
| 146 | + << std::setw(16) << bswap64(v64) << '\n'; |
| 147 | + std::cout << std::dec << std::setfill(' '); |
| 148 | + |
| 149 | +#if defined(__cpp_lib_byteswap) && __cpp_lib_byteswap >= 202110L |
| 150 | + std::cout << "std::byteswap available (__cpp_lib_byteswap = " |
| 151 | + << __cpp_lib_byteswap << ")\n"; |
| 152 | + std::cout << std::hex << std::setfill('0'); |
| 153 | + std::cout << " std::byteswap<uint32_t>(0xDEADBEEF) = 0x" |
| 154 | + << std::setw(8) << std::byteswap(v32) << '\n'; |
| 155 | + std::cout << std::dec << std::setfill(' '); |
| 156 | +#else |
| 157 | + std::cout << "std::byteswap NOT available on this toolchain (needs C++23).\n" |
| 158 | + << " Falling back to manual bswap32/16/64 above.\n"; |
| 159 | +#endif |
| 160 | +} |
| 161 | + |
| 162 | +// --- Robotics framing -------------------------------------------------------- |
| 163 | + |
| 164 | +// A CAN 2.0B frame layout (simplified): 29-bit extended ID + up to 8 data bytes. |
| 165 | +// The CAN bus itself transmits bits, but when a controller hands you a frame |
| 166 | +// struct you often need to interpret the payload as a multi-byte integer with |
| 167 | +// a documented endianness (CANopen is little-endian, J1939 signals are LE too, |
| 168 | +// but many OEM protocols pack big-endian "Motorola" signals). |
| 169 | +struct CanFrame { |
| 170 | + std::uint32_t id; // host order |
| 171 | + std::uint8_t dlc; // data length code, 0..8 |
| 172 | + std::array<std::uint8_t, 8> data; // raw payload bytes |
| 173 | +}; |
| 174 | + |
| 175 | +void demo_can_frame() { |
| 176 | + banner("5. Robotics: CAN frame with a big-endian (Motorola) signal"); |
| 177 | + // Suppose byte 0..3 of the payload carry a 32-bit wheel-encoder count in |
| 178 | + // big-endian order, byte 4..5 a 16-bit motor temperature in BE, deg*100. |
| 179 | + std::uint32_t encoder_counts = 0x0001F4A0u; // 128'160 counts |
| 180 | + std::uint16_t motor_temp_cdg = 0x1F40u; // 80.00 deg C (8000) |
| 181 | + |
| 182 | + CanFrame f{}; |
| 183 | + f.id = 0x18FF50E5u; // J1939-style 29-bit extended ID |
| 184 | + f.dlc = 6; |
| 185 | + |
| 186 | + // Pack signals into the wire (big-endian). |
| 187 | + std::uint32_t enc_be = host_to_be(encoder_counts); |
| 188 | + std::uint16_t temp_be = host_to_be(motor_temp_cdg); |
| 189 | + std::memcpy(f.data.data() + 0, &enc_be, sizeof(enc_be)); |
| 190 | + std::memcpy(f.data.data() + 4, &temp_be, sizeof(temp_be)); |
| 191 | + |
| 192 | + std::cout << "CAN id = 0x" << std::hex << f.id << std::dec |
| 193 | + << ", dlc = " << static_cast<int>(f.dlc) << '\n'; |
| 194 | + std::cout << "payload bytes (on wire): " << std::hex << std::setfill('0'); |
| 195 | + for (std::uint8_t i = 0; i < f.dlc; ++i) { |
| 196 | + std::cout << std::setw(2) << static_cast<int>(f.data[i]) << ' '; |
| 197 | + } |
| 198 | + std::cout << std::dec << std::setfill(' ') << '\n'; |
| 199 | + |
| 200 | + // Decode side: pull bytes off the wire and convert back to host order. |
| 201 | + std::uint32_t enc_wire = 0; |
| 202 | + std::uint16_t temp_wire = 0; |
| 203 | + std::memcpy(&enc_wire, f.data.data() + 0, sizeof(enc_wire)); |
| 204 | + std::memcpy(&temp_wire, f.data.data() + 4, sizeof(temp_wire)); |
| 205 | + std::cout << "decoded encoder counts = " << be_to_host(enc_wire) << '\n'; |
| 206 | + std::cout << "decoded motor temp = " |
| 207 | + << (be_to_host(temp_wire) / 100.0) << " degC\n"; |
| 208 | +} |
| 209 | + |
| 210 | +void demo_network_header() { |
| 211 | + banner("6. Robotics: network protocol header (network byte order = BE)"); |
| 212 | + // ROS 1 TCPROS, custom UDP telemetry, and most IP-layer protocols use |
| 213 | + // big-endian on the wire. htons/htonl exist precisely for this. |
| 214 | + std::uint16_t host_port = 11311; // ROS master default port |
| 215 | + std::uint16_t wire_port = host_to_be(host_port); |
| 216 | + |
| 217 | + std::cout << "host port : " << host_port |
| 218 | + << " (0x" << std::hex << host_port << std::dec << ")\n"; |
| 219 | + std::cout << "wire port : 0x" << std::hex << wire_port << std::dec << '\n'; |
| 220 | + print_memory_bytes(host_port, " host_port"); |
| 221 | + print_memory_bytes(wire_port, " wire_port"); |
| 222 | +} |
| 223 | + |
| 224 | +void demo_embedded_sensor_word() { |
| 225 | + banner("7. Robotics: embedded sensor word with std::bit_cast"); |
| 226 | + // A SPI IMU often returns a 16-bit two's-complement reading MSB-first. |
| 227 | + // Bytes arrive as {hi, lo}; reinterpret without aliasing UB. |
| 228 | + std::array<std::uint8_t, 2> rx_buffer{0xFF, 0x80}; // -128 raw count, BE |
| 229 | + |
| 230 | + std::uint16_t raw_be{}; |
| 231 | + std::memcpy(&raw_be, rx_buffer.data(), sizeof(raw_be)); |
| 232 | + std::uint16_t raw_host = be_to_host(raw_be); |
| 233 | + auto signed_count = std::bit_cast<std::int16_t>(raw_host); |
| 234 | + |
| 235 | + std::cout << "rx bytes : 0x" << std::hex << static_cast<int>(rx_buffer[0]) |
| 236 | + << ' ' << std::hex << static_cast<int>(rx_buffer[1]) << std::dec << '\n'; |
| 237 | + std::cout << "raw_host : 0x" << std::hex << raw_host << std::dec << '\n'; |
| 238 | + std::cout << "signed : " << signed_count << " counts\n"; |
| 239 | +} |
| 240 | + |
| 241 | +// --- main -------------------------------------------------------------------- |
| 242 | + |
| 243 | +int main() { |
| 244 | + std::cout << "Endianness demos (companion to docs/big-endian_little-endian.md)\n"; |
| 245 | + |
| 246 | + demo_runtime_detection(); |
| 247 | + demo_compile_time_detection(); |
| 248 | + demo_byte_layout(); |
| 249 | + demo_byteswap(); |
| 250 | + demo_can_frame(); |
| 251 | + demo_network_header(); |
| 252 | + demo_embedded_sensor_word(); |
| 253 | + |
| 254 | + std::cout << "\nDone.\n"; |
| 255 | + return 0; |
| 256 | +} |
0 commit comments