Endianness (also called byte order) describes the order in which the bytes of a multi-byte value are stored in memory or represented in a data stream; how bytes are ordered during transmission depends on the protocol or device convention. In big-endian order the most-significant byte occupies the lowest address; in little-endian order the least-significant byte does.
In practice
Most general-purpose embedded processors use a fixed endianness: x86/x64, ARM Cortex-M, and RISC-V are little-endian in the most common configurations, while many legacy Motorola 68k and older PowerPC parts are big-endian. Some processors (including certain ARM Cortex-A and MIPS cores) are bi-endian and can be configured at boot time, though the exact mechanism and configuration point vary by core and SoC implementation. Knowing your target's endianness is essential whenever you access a multi-byte variable as raw bytes, cast between pointer types, or use unions to inspect individual bytes of a wider type.
Endianness becomes an active problem at system boundaries. When a little-endian MCU communicates with a big-endian peripheral, a network stack, or another processor, multi-byte fields must be byte-swapped. Network protocols (TCP/IP) define big-endian as "network byte order," so embedded TCP/IP stacks on little-endian MCUs must call conversion functions such as htons()/htonl() and ntohs()/ntohl(). The EmbeddedRelated post "Endianness and Serial Communication" covers this boundary problem in detail.
A common pitfall is silently ignoring endianness when casting a byte buffer to a struct, or when reading a sensor register that returns a 16-bit value in a specific byte order. Reading a big-endian sensor over SPI on a little-endian MCU without swapping bytes produces a numerically wrong result with no compile-time warning. Symptoms are often subtle: for a swapped 16-bit value the result may appear off by roughly a factor of 256, though the magnitude and nature of the error depend on the value width and the actual data.
Endianness also affects binary file formats, firmware images, and flash storage layouts. If a firmware image is generated on a host with the opposite endianness from the target (rare but possible in cross-compilation toolchains with misconfigured flags), multi-byte constants in initialized data sections will be wrong. Linker scripts and compiler flags (e.g., -mlittle-endian / -mbig-endian in GCC ARM) must match the target configuration.
Discussed on EmbeddedRelated
Frequently asked
How do I detect the endianness of my target at runtime?
A classic technique is to store a known multi-byte value and then inspect its individual bytes. For example: store 0x0001 in a uint16_t, then read the first byte using memcpy() into a uint8_t (the most portable approach) or via a uint8_t pointer with care to avoid strict-aliasing violations; if it equals 0x01 the system is
little-endian, if it equals 0x00 it is
big-endian. Many
toolchains also define preprocessor macros such as __BYTE_ORDER__, __LITTLE_ENDIAN__, or vendor-specific equivalents that let you make the determination at compile time without any runtime cost.
Does endianness affect single-byte values or bitfields?
Single bytes are unaffected because there is only one byte to order. Bitfields are a more subtle case: the C standard leaves the bit ordering within a bitfield implementation-defined, and compilers may pack bits from the LSB or MSB end depending on the ABI. Relying on bitfield layout to map directly to hardware
registers or protocol fields is non-portable and should generally be avoided; explicit masking and shifting is safer and more readable.
What byte-swap utilities are available in C for embedded targets?
The
POSIX functions htons(), htonl(), ntohs(), and ntohl() convert 16- and 32-bit values between host and network (
big-endian) byte order, and most embedded
TCP/IP stacks provide them.
GCC also offers __builtin_bswap16(), __builtin_bswap32(), and __builtin_bswap64(), which on supported targets and with sufficient optimization can compile to a single-instruction swap (e.g., REV on ARM Cortex-M3 and later), though on other cores or at lower optimization levels they may expand to multiple instructions. CMSIS provides __REV() and __REV16() wrappers for ARM Cortex-M cores.
I am reading a 16-bit value from a sensor over I2C and it looks wrong. Could endianness be the cause?
Yes, this is a very common issue. Many sensors return the high byte first (
big-endian), while most ARM Cortex-M MCUs are
little-endian. If you store the two received bytes in memory order and then cast that memory to a uint16_t without swapping, your value will have its bytes reversed. Check the sensor datasheet for byte order, and swap manually or with a byte-swap intrinsic if needed. The EmbeddedRelated post 'Endianness and Serial Communication' addresses this scenario directly.
Can I use a union to safely swap or inspect bytes in C?
In
C99 and C11, reading from a union member other than the last one written is technically undefined behavior in strict interpretations, but in practice nearly all embedded C compilers (
GCC, Clang, IAR, KEIL/AC6) define and support type-punning through unions as a deliberate extension. It is widely used in embedded code to inspect individual bytes of a float or integer. If strict portability is required, use memcpy() between types instead; a good compiler will optimize it to the same machine code.
Differentiators vs similar concepts
Endianness describes byte order within a multi-byte value in memory. It is distinct from bit order (which bit within a byte is transmitted first on a serial line), which is a separate property controlled by peripheral configuration (e.g., the LSBFIRST bit in an
SPI CR register). The two properties are independent: a
little-endian MCU can transmit bits MSB-first over SPI, and a
big-endian MCU can transmit bits LSB-first. Confusing byte order with bit order is a common source of bugs when debugging raw serial captures.