Every time a computer stores a number bigger than a single byte in memory, it has to decide the order in which the bytes go. That decision — endianness — sounds trivial, but it’s a source of subtle, hard-to-debug problems in networking, file formats, cross-platform code, and firmware. ARM, uniquely among mainstream architectures, actually supports both byte orders. This article explains what endianness is, how ARM implements it, and why it still matters today.
What Is Endianness?
Endianness describes the order in which bytes of a multi-byte value (like a 32-bit integer) are stored in memory.
Take the 32-bit hexadecimal value 0x12345678. It’s made up of four bytes: 12, 34, 56, 78.
- Big-endian: the most significant byte (the “big end,”
12) is stored at the lowest memory address. - Little-endian: the least significant byte (the “little end,”
78) is stored at the lowest memory address.
Address: 0x00 0x01 0x02 0x03
Big-endian: 12 34 56 78
Little-endian: 78 56 34 12
The term comes from Jonathan Swift’s Gulliver’s Travels, where a war breaks out over which end of a boiled egg to crack — “big-endians” vs. “little-endians.” Danny Cohen borrowed the metaphor in a famous 1980 paper on byte-order arguments in computer networking, and the name stuck.
Why Does Endianness Exist At All?
Different early processor designers chose different conventions based on hardware implementation trade-offs — how carry propagation worked in arithmetic units, how it simplified type-casting between different sized integers, and how it interacted with I/O hardware. Neither choice is objectively “better” for all purposes:
- Little-endian makes it trivial to reinterpret a value as a smaller type — the address of a
uint32_tand the address of its lowest byte (if truncated touint8_t) are the same. This is convenient for arithmetic-heavy code. - Big-endian matches the natural left-to-right reading order humans use for numbers, which makes memory dumps easier to read and matches “network byte order” — the standard used in TCP/IP headers.
Endianness in the ARM Architecture
Unlike x86 (which is always little-endian) or older SPARC (always big-endian), ARM is bi-endian. This means the architecture defines mechanisms for operating in either mode, and the actual endianness in effect can, on many ARM implementations, be configured — sometimes even switched at runtime.
BE-32 and BE-8: The Two Big-Endian Flavors
ARM’s history here is genuinely a bit messy, and understanding it requires knowing two distinct big-endian models it has supported over the decades:
- BE-32 (word-invariant): Used on older ARM architecture versions (ARMv5 and earlier). In this model, only the byte lanes within a word are reordered — word (32-bit) accesses “look” the same regardless of endianness, but byte and halfword accesses land at unintuitive addresses. This scheme is now obsolete.
- BE-8 (byte-invariant): Introduced with ARMv6 and used by all modern ARM cores (ARMv7, ARMv8/AArch64, and beyond). In BE-8, byte addresses always refer to the same byte of data regardless of endianness — only the ordering of bytes within a register load/store is affected. This matches how big-endian works on virtually every other modern big-endian-capable architecture (like PowerPC), making cross-platform interoperability far more sane.
How ARM Selects Endianness
Modern ARM cores (ARMv7 and ARMv8/AArch64) determine endianness through configuration bits:
- On AArch32 (32-bit ARM state), the
CPSR(Current Program Status Register) contains an E bit that controls the endianness for data accesses; there’s also aSETENDinstruction (deprecated in later revisions) that could switch it. - On AArch64 (64-bit ARM state, ARMv8+), endianness is controlled via the
SCTLR_ELx(System Control Register)EEandE0Ebits, set at boot/kernel level, typically fixed for the lifetime of the system rather than toggled at the application level.
Critically, in almost every real ARM deployment — Android phones, iPhones, embedded Linux boards, Raspberry Pi — the system is configured to run in little-endian mode, because:
- It’s compatible with the overwhelmingly little-endian software ecosystem (most C/C++ code, most Linux distributions, and all mainstream compilers default to assuming little-endian on ARM).
- It matches the byte order used by x86, easing porting of software between desktop/server (x86) and mobile/embedded (ARM) platforms.
So while ARM can do big-endian, and it’s occasionally used in specialized embedded/networking equipment (some network processors and legacy telecom gear run ARM in BE-8 mode for compatibility with big-endian protocol stacks or legacy code), the vast majority of ARM devices you’ll ever touch — Android phones, iPads, Raspberry Pis, ARM-based Chromebooks, AWS Graviton servers — run little-endian.
Instructions Sensitive to Endianness
Endianness only affects how multi-byte memory accesses are interpreted — single-byte loads/stores (LDRB/STRB in ARM assembly) are unaffected. It matters for:
- Word loads/stores (
LDR/STR, 32-bit) - Halfword loads/stores (
LDRH/STRH, 16-bit) - Doubleword and larger loads (
LDRD, NEON/SIMD vector loads)
Practical Implications and Examples
Networking
Network protocols like TCP/IP define network byte order as big-endian. An ARM device (running little-endian, as almost all do) must byte-swap multi-byte fields — IP addresses, port numbers — when constructing or parsing packets. This is why C code universally uses functions like htons() (host-to-network-short) and ntohl() (network-to-host-long) rather than assuming a byte order. These functions are effectively no-ops on big-endian systems and perform an actual byte swap on little-endian systems like standard ARM Linux/Android builds.
#include <arpa/inet.h>
uint16_t port = 8080;
uint16_t network_order_port = htons(port); // byte-swaps on little-endian ARM
File Formats
Some binary file formats (like older .bmp variants, Java .class files, and certain audio/network capture formats) specify big-endian fields. Code that parses these on ARM must explicitly byte-swap, typically via __builtin_bswap32() (GCC/Clang intrinsic) or ARM’s dedicated REV instruction, which reverses byte order in a register in a single cycle.
; ARM assembly: reverse byte order of a 32-bit register
REV r1, r0 ; r1 = byte-swapped r0
Cross-Compilation and Debugging
When cross-compiling for ARM targets (e.g., building Android NDK code, or embedded Linux firmware) on an x86 development machine, both are little-endian by default, so raw memory dumps and struct layouts usually “just work” without manual byte-swapping — a major convenience that’s part of why little-endian ARM became the mobile/embedded standard.
Real-World Use in Legacy and Networking Equipment
Historically, some networking hardware (routers, switches) using ARM or MIPS cores ran in big-endian mode to match legacy network protocol stacks and existing big-endian codebases inherited from earlier PowerPC- or MIPS-based designs. Modern networking gear has largely converged on little-endian ARM as well, but you may still encounter BE-8 configurations in specialized telecom or industrial control firmware.
Comparing Endianness Across Architectures
| Architecture | Default Endianness | Notes |
|---|---|---|
| x86 / x86-64 | Little-endian | Fixed, no big-endian mode |
| ARM (ARMv7, AArch32) | Configurable (BE-8/LE) | Little-endian used almost universally in practice |
| ARM (AArch64, ARMv8+) | Configurable (BE-8/LE) | Little-endian default on virtually all consumer devices |
| PowerPC | Configurable | Often big-endian historically, though PowerPC64 supports LE |
| MIPS | Configurable | Both variants (MIPSEB/MIPSEL) used historically |
| SPARC | Big-endian | Fixed |
| RISC-V | Configurable (spec allows both) | Little-endian is the common default |
Troubleshooting Endianness Issues
- Symptom: A value read from a file or network socket looks “scrambled” or wildly wrong. Almost always a missed byte-swap. Check whether the data source specifies a fixed byte order (many binary formats do) and verify your code swaps appropriately for the host’s endianness.
- Symptom: Code works on x86 but breaks (or vice versa) when ported to/from ARM. This is usually not actually an endianness bug, since most ARM systems are little-endian too — but it’s worth ruling out if you’re dealing with an unusual embedded ARM target explicitly configured for big-endian.
- Symptom: Struct/union type punning gives different results across platforms. Reinterpreting the bytes of an integer as a byte array (or vice versa) is inherently endian-dependent; avoid relying on this pattern in portable code, and use explicit bit-shifting/masking instead.
Best Practices
- Never assume endianness in code meant to be portable — use explicit serialization functions (
htons/ntohl, or a well-tested serialization library) rather than raw pointer casts. - Use fixed-width, explicitly-ordered formats (like Protocol Buffers, MessagePack, or well-specified binary formats) for data interchange to sidestep endianness ambiguity entirely.
- Leverage compiler intrinsics (
__builtin_bswap16/32/64in GCC/Clang) rather than hand-rolled byte-swapping loops — they compile down to a single efficient instruction (like ARM’sREV) instead of multiple shifts and masks. - Document assumed byte order in any custom binary protocol or file format your project defines.
- Test on both simulated endian modes if you’re building embedded or networking software that might run on a big-endian ARM configuration, since bugs here are notoriously easy to miss in day-to-day little-endian development.
Summary
Endianness is the convention that decides byte ordering for multi-byte values in memory, and ARM is unusual in that it architecturally supports both big-endian and little-endian modes — a legacy of its BE-32 and modern BE-8 implementations. In practice, nearly every ARM device you interact with today, from smartphones to Raspberry Pis to cloud ARM servers, runs little-endian, largely for compatibility with the dominant little-endian software ecosystem built around x86. Still, understanding ARM’s bi-endian capability matters for networking code, binary file parsing, and specialized embedded/telecom systems where big-endian configurations persist.
FAQs
Q: Is ARM big-endian or little-endian? ARM supports both, but essentially all consumer and mobile ARM devices (Android, iOS, Raspberry Pi, cloud ARM servers) run in little-endian mode.
Q: What’s the difference between BE-32 and BE-8? BE-32 is an older, word-invariant big-endian scheme used up to ARMv5; BE-8 is the modern, byte-invariant scheme used from ARMv6 onward and is the only big-endian mode available on current ARM cores.
Q: Does endianness affect single-byte data? No — a single byte has no internal ordering, so byte-level reads/writes (LDRB/STRB) are unaffected by endianness settings.
Q: Why is “network byte order” big-endian if most devices are little-endian? It’s a historical convention from early Internet protocol design (RFC 1700) that predates the dominance of little-endian hardware; it remains standardized for backward compatibility, so little-endian systems must convert accordingly.
Q: Can an ARM CPU switch endianness at runtime? Older AArch32 cores technically supported the SETEND instruction for a runtime switch (now deprecated), but in practice, endianness on real systems is fixed at boot/configuration time by firmware and the OS, not toggled dynamically by applications.
References
- ARM Architecture Reference Manual (ARMv8-A), Section on Endianness
- ARM Developer Documentation — “Bi-endian support” (developer.arm.com)
- Danny Cohen, “On Holy Wars and a Plea for Peace,” IEEE Computer, 1981
- RFC 1700 — Assigned Numbers (defines network byte order)
- Linux kernel documentation on
CONFIG_CPU_BIG_ENDIAN
