Big Endian vs Little Endian: How Byte Order Affects Your Code
Take the number 0x12345678. It is 4 bytes. In memory, those bytes have to go somewhere — but which one goes first? The answer depends on whether your machine is big-endian or little-endian, and getting it wrong silently corrupts your data.
I learned this the hard way while reading a binary file format on an ARM microcontroller. The file was written on x86 (little-endian) but my parser assumed big-endian. Every integer was byteswapped. Took me three hours to realize the bug was one line: I forgot ntohl().
What Endianness Means
Endianness is the order in which bytes are stored in memory for multi-byte values. It does not affect the order of bits within a byte — bits are always numbered from LSB (bit 0) to MSB (bit 7). It is strictly about byte ordering.
| Architecture | Endianness | Example (0x12345678) |
|---|---|---|
| x86 / x86-64 | Little-endian | 78 56 34 12 |
| ARM (default) | Little-endian | 78 56 34 12 |
| ARM (bi-endian mode) | Configurable | Either |
| PowerPC (older Macs) | Big-endian | 12 34 56 78 |
| Network byte order | Big-endian | 12 34 56 78 |
| Java VM | Big-endian | 12 34 56 78 |
The terms come from Jonathan Swift's Gulliver's Travels, where two factions war over which end of an egg to crack. In computing, it is the same petty argument — but it has real consequences.
Why x86 Chose Little-Endian
Danny Cohen's 1980 paper "On Holy Wars and a Plea for Peace" (the origin of the "endian" term) lays out the arguments. Little-endian has one compelling advantage: you can add multi-byte numbers starting from the lowest address.
// Adding 0x12345678 + 0x00000001 in little-endian memory:
// Address: 00 01 02 03
// Value A: 78 56 34 12
// Value B: 01 00 00 00
//
// The CPU starts at address 00: 0x78 + 0x01 = 0x79
// Carry propagates naturally through addresses 01, 02, 03.
// This is simpler in hardware than big-endian addition.
Another advantage: pointer casting works naturally. In little-endian, if you cast a uint32_t* to a uint8_t*, the byte you read is the least significant byte — which is usually what you want.
uint32_t val = 0x12345678;
uint8_t low_byte = *(uint8_t*)&val;
// Little-endian: low_byte = 0x78 (the LSB)
// Big-endian: low_byte = 0x12 (the MSB) — surprising!
Detecting Endianness in C
#include <stdint.h>
#include <stdio.h>
int is_little_endian(void) {
uint16_t x = 0x0001;
return *(uint8_t*)&x == 0x01;
}
int main() {
if (is_little_endian())
printf("Little-endian (x86, ARM default)\n");
else
printf("Big-endian (network order, some embedded)\n");
}
When Endianness Bites You
1. Network Programming
TCP/IP uses big-endian (network byte order). Every time you set a port number or IP address in a socket struct, you must convert from host byte order. The functions are htons() (host to network short), htonl() (host to network long), and their inverses ntohs(), ntohl().
struct sockaddr_in addr;
addr.sin_port = htons(8080); // Must convert!
addr.sin_addr.s_addr = inet_addr("10.0.0.1"); // inet_addr already returns network order
2. Binary File Formats
File formats pick one endianness and stick to it:
- BMP — little-endian (native to x86)
- JPEG — big-endian (all markers are big-endian)
- PNG — big-endian (all multi-byte integers in headers)
- ELF (Linux executables) — matches the target architecture
- WAV — little-endian (RIFF format)
3. Cross-Platform Serialization
If you send raw structs between a little-endian x86 server and a big-endian embedded device, your integers will arrive byteswapped. The fix: always serialize to a defined byte order, or use a format like Protobuf/JSON that handles this for you.