Endianness: Big-Endian vs Little-Endian
Understand byte order in multi-byte data. Learn the difference between big-endian and little-endian, how to detect endianness, and convert between formats for networking and file I/O.
Track Your Progress
Sign in to save your learning progress
What You Will Learn
- ✓Understand big-endian vs little-endian
- ✓Detect system endianness at runtime
- ✓Convert between byte orders
- ✓Handle endianness in network programming
- ✓Write portable binary file code
01Introduction
When you store a multi-byte number like 0x12345678 in memory, which byte comes first? The answer depends on your system's endianness — and getting it wrong can corrupt your data when communicating between different systems or reading binary files.
What You'll Learn
- • The difference between big-endian and little-endian
- • How to detect your system's endianness
- • Converting between byte orders
- • Writing portable code for network and file I/O
02What is Endianness?
Endianness (or byte order) refers to the order in which bytes of a multi-byte value are stored in memory. There are two main types:
Big-Endian (BE)
The most significant byte (MSB) is stored at the lowest address. Like reading left-to-right.
Addr 1: 34
Addr 2: 56
Addr 3: 78 (LSB)
Used by: Network protocols, Java, PowerPC, SPARC
Little-Endian (LE)
The least significant byte (LSB) is stored at the lowest address. Like reading right-to-left.
Addr 1: 56
Addr 2: 34
Addr 3: 12 (MSB)
Used by: x86, x64, ARM (default), most modern PCs
Origin of the Names
The terms come from Gulliver's Travels where two groups fought over which end of an egg to crack first — the "big end" or "little end." Computer scientists adopted this whimsical reference for byte order.
03Detecting Endianness at Runtime
Here are several ways to detect your system's byte order:
1#include <stdio.h>2#include <stdint.h>34// Method 1: Using a union5int is_little_endian_union(void) {6 union {7 uint32_t value;8 uint8_t bytes[4];9 } test = { .value = 0x01020304 };10 11 return test.bytes[0] == 0x04; // LSB first = little-endian12}1314// Method 2: Using pointer casting15int is_little_endian_pointer(void) {16 uint32_t value = 0x01020304;17 uint8_t *byte_ptr = (uint8_t*)&value;18 19 return *byte_ptr == 0x04; // First byte is LSB = little-endian20}2122// Method 3: Compile-time check (GCC/Clang)23#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__24 #define IS_LITTLE_ENDIAN 125#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__26 #define IS_LITTLE_ENDIAN 027#else28 #define IS_LITTLE_ENDIAN is_little_endian_union()29#endif3031int main() {32 printf("System is %s-endian\n", 33 is_little_endian_union() ? "little" : "big");34 35 // Visualize the bytes36 uint32_t value = 0x12345678;37 uint8_t *bytes = (uint8_t*)&value;38 39 printf("\nValue: 0x%08X\n", value);40 printf("Memory layout:\n");41 for (int i = 0; i < 4; i++) {42 printf(" Address +%d: 0x%02X\n", i, bytes[i]);43 }44 45 return 0;46}Output on Little-Endian System (most PCs):
Value: 0x12345678
Memory layout:
Address +0: 0x78
Address +1: 0x56
Address +2: 0x34
Address +3: 0x12
04Why Endianness Matters
Endianness becomes critical in these scenarios:
1. Network Programming
Network protocols use big-endian (network byte order). If your system is little-endian, you must convert before sending and after receiving.
2. Binary File I/O
Files written on one system may be read on another with different endianness. Formats like PNG, JPEG, and ELF specify their byte order.
3. Cross-Platform Communication
Embedded systems (often big-endian) communicating with PCs (little-endian) need explicit byte order handling.
4. Memory-Mapped Hardware
Hardware registers may expect a specific byte order regardless of CPU architecture.
1// Example: Sending data over network2// WRONG: Sends bytes in host order (may be little-endian)3uint32_t port = 8080;4send(socket, &port, sizeof(port), 0); // Bug on little-endian!56// CORRECT: Convert to network byte order first7uint32_t port_network = htonl(8080); // Host TO Network Long8send(socket, &port_network, sizeof(port_network), 0); // Always works!05Byte Order Conversion Functions
The standard library provides functions to convert between host and network byte order:
| Function | Description | Size |
|---|---|---|
htons() | Host to Network Short | 16-bit |
htonl() | Host to Network Long | 32-bit |
ntohs() | Network to Host Short | 16-bit |
ntohl() | Network to Host Long | 32-bit |
1#include <stdio.h>2#include <stdint.h>3#include <arpa/inet.h> // POSIX: htonl, htons, ntohl, ntohs4// On Windows: #include <winsock2.h>56int main() {7 uint16_t port = 8080;8 uint32_t ip = 0xC0A80001; // 192.168.0.19 10 // Convert to network byte order11 uint16_t port_net = htons(port);12 uint32_t ip_net = htonl(ip);13 14 printf("Host byte order:\n");15 printf(" Port: 0x%04X\n", port);16 printf(" IP: 0x%08X\n", ip);17 18 printf("\nNetwork byte order:\n");19 printf(" Port: 0x%04X\n", port_net);20 printf(" IP: 0x%08X\n", ip_net);21 22 // Convert back to host byte order23 printf("\nConverted back:\n");24 printf(" Port: %u\n", ntohs(port_net));25 printf(" IP: 0x%08X\n", ntohl(ip_net));26 27 return 0;28}Output on Little-Endian System:
Port: 0x1F90
IP: 0xC0A80001
Network byte order:
Port: 0x901F
IP: 0x0100A8C0
Converted back:
Port: 8080
IP: 0xC0A80001
06Manual Byte Swapping
For 64-bit values or when you need more control, you can swap bytes manually:
1#include <stdio.h>2#include <stdint.h>34// Swap 16-bit value5uint16_t swap16(uint16_t value) {6 return (value << 8) | (value >> 8);7}89// Swap 32-bit value10uint32_t swap32(uint32_t value) {11 return ((value & 0x000000FF) << 24) |12 ((value & 0x0000FF00) << 8) |13 ((value & 0x00FF0000) >> 8) |14 ((value & 0xFF000000) >> 24);15}1617// Swap 64-bit value18uint64_t swap64(uint64_t value) {19 return ((value & 0x00000000000000FFULL) << 56) |20 ((value & 0x000000000000FF00ULL) << 40) |21 ((value & 0x0000000000FF0000ULL) << 24) |22 ((value & 0x00000000FF000000ULL) << 8) |23 ((value & 0x000000FF00000000ULL) >> 8) |24 ((value & 0x0000FF0000000000ULL) >> 24) |25 ((value & 0x00FF000000000000ULL) >> 40) |26 ((value & 0xFF00000000000000ULL) >> 56);27}2829// GCC/Clang built-ins (more efficient)30// __builtin_bswap16(x), __builtin_bswap32(x), __builtin_bswap64(x)3132int main() {33 uint32_t value = 0x12345678;34 printf("Original: 0x%08X\n", value);35 printf("Swapped: 0x%08X\n", swap32(value));36 37 return 0;38}07Portable Binary File I/O
When writing binary files that may be read on different systems, always use a defined byte order:
1#include <stdio.h>2#include <stdint.h>3#include <arpa/inet.h>45// Write a 32-bit integer in big-endian format6void write_be32(FILE *f, uint32_t value) {7 uint32_t be_value = htonl(value);8 fwrite(&be_value, sizeof(be_value), 1, f);9}1011// Read a 32-bit integer in big-endian format12uint32_t read_be32(FILE *f) {13 uint32_t be_value;14 fread(&be_value, sizeof(be_value), 1, f);15 return ntohl(be_value);16}1718// Alternatively: Write byte-by-byte (most portable)19void write_be32_portable(FILE *f, uint32_t value) {20 uint8_t bytes[4];21 bytes[0] = (value >> 24) & 0xFF; // MSB first22 bytes[1] = (value >> 16) & 0xFF;23 bytes[2] = (value >> 8) & 0xFF;24 bytes[3] = value & 0xFF; // LSB last25 fwrite(bytes, 1, 4, f);26}2728uint32_t read_be32_portable(FILE *f) {29 uint8_t bytes[4];30 fread(bytes, 1, 4, f);31 return ((uint32_t)bytes[0] << 24) |32 ((uint32_t)bytes[1] << 16) |33 ((uint32_t)bytes[2] << 8) |34 ((uint32_t)bytes[3]);35}3637int main() {38 // Write file39 FILE *f = fopen("data.bin", "wb");40 if (f) {41 write_be32(f, 0x12345678);42 write_be32(f, 1000000);43 fclose(f);44 }45 46 // Read file47 f = fopen("data.bin", "rb");48 if (f) {49 printf("Value 1: 0x%08X\n", read_be32(f));50 printf("Value 2: %u\n", read_be32(f));51 fclose(f);52 }53 54 return 0;55}Best Practice
The byte-by-byte approach is the most portable — it works correctly regardless of system endianness and avoids any alignment issues.
!Common Pitfalls
1. Assuming Endianness
Never assume your code will only run on little-endian (or big-endian) systems. Always use explicit conversion when dealing with external data.
2. Forgetting to Convert
// WRONG: Direct struct write to filestruct Data { int32_t x, y; };fwrite(&data, sizeof(data), 1, file); // Endianness-dependent!// CORRECT: Convert each fieldwrite_be32(file, data.x);write_be32(file, data.y);3. Using Wrong Size Function
// WRONG: Using htonl for 16-bit valueuint16_t port = htonl(8080); // Undefined behavior!// CORRECT: Use htons for 16-bituint16_t port = htons(8080);08Summary
Key Takeaways:
- ✓Big-endian stores MSB first; Little-endian stores LSB first
- ✓Most modern PCs (x86/x64) are little-endian
- ✓Network protocols use big-endian (network byte order)
- ✓Use
htons/htonlandntohs/ntohlfor network conversion - ✓For binary files, define a byte order and convert explicitly
- ✓Byte-by-byte I/O is the most portable approach
Test Your Knowledge
Related Tutorials
Memory Alignment & Padding
Understand why compilers add padding to structures. Learn about memory alignment requirements, how to minimize padding, and packed structures for embedded systems.
Bitmasks and Bit Manipulation
Work with individual bits! Set, clear, toggle, and check bits. Implement permission systems and flags efficiently.
Pointers in C
The most powerful feature in C! Pointers store memory addresses. Learn what they are, why they matter, and how to use them safely.
Have Feedback?
Found something missing or have ideas to improve this tutorial? Let us know on GitHub!