C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
211: Endianness and Byte Order Handling
Up until now, we've treated integers as abstract values. You give the computer 10, and it gives you 10 back. But when you start sending data over a network or saving it to a binary file that might be opened on a different CPU architecture, that abstraction disappears. You have to care about endiannessβthe order in which bytes are stored in memory.
If you're on a modern x86 or ARM processor, you're using Little Endian. This means the least significant byte is stored at the lowest address. If you send that raw memory to a Big Endian system (common in older network gear or some RISC architectures), the other side will read your bytes in reverse, and your "1" will suddenly become "16,777,216".
Visualizing the Byte Flip
Let's build a small tool to see this in action. I want to see exactly how a 32-bit integer looks in my RAM. I'll use a uint32_t and a unsigned char* pointer to peek at the individual bytes.
#include <stdio.h>
#include <stdint.h>
int main() {
uint32_t value = 0x12345678;
unsigned char *p = (unsigned char *)&value;
printf("Value: 0x%08X\n", value);
printf("Byte 0: 0x%02X\n", p[0]);
printf("Byte 1: 0x%02X\n", p[1]);
printf("Byte 2: 0x%02X\n", p[2]);
printf("Byte 3: 0x%02X\n", p[3]);
return 0;
}
On my machine, this prints 78, 56, 34, 12. The bytes are backwards. That's Little Endian. If this were a Big Endian machine, it would print 12, 34, 56, 78. This is why "raw" memory dumps are confusing if you don't know the architecture.
The "Roll Your Own" Mistake
Imagine we're writing a network driver. Network protocols almost universally use Big Endian (often called "Network Byte Order"). Since I'm on a Little Endian machine, I need to flip these bytes before I send them. I'll try to write a quick swap function.
uint32_t swap_uint32(uint32_t val) {
return ((val & 0x000000FF) << 24) |
((val & 0x0000FF00) << 8) |
((val & 0x00FF0000) >> 8) |
((val & 0xFF000000) >> 24);
}
I ran this, and it seemed to work for 0x12345678. But then I tried it with a value that had the high bit set, and I noticed some bizarre behavior with sign extension because I forgot that I was occasionally dealing with signed integers in my test wrapper. More importantly, I realized I was spending time debugging bit-shifts when C already provides a standard way to do this.
Writing your own swap function is a great academic exercise, but in production, it's a liability. You're more likely to make a typo in a hex mask than you are to find a bug in the standard library.
Letting the Standard Library Handle the Heavy Lifting
For networking, we use arpa/inet.h (on Unix/Linux) which provides four essential functions: htonl (host to network long), htons (host to network short), ntohl (network to host long), and ntohs (network to host short).
The beauty of these functions is that they are conditional. If you compile your code on a Big Endian machine, htonl is defined as a macro that does absolutely nothing, because the host order is already the network order. If you're on Little Endian, it performs the swap. Your code becomes portable.
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
int main() {
uint32_t host_val = 0x12345678;
// Convert host order to network order (Big Endian)
uint32_t net_val = htonl(host_val);
printf("Host: 0x%08X -> Network: 0x%08X\n", host_val, net_val);
// Convert back
uint32_t back_again = ntohl(net_val);
printf("Back to host: 0x%08X\n", back_again);
return 0;
}
Now, regardless of whether the code runs on a Raspberry Pi, an Intel server, or an old PowerPC Mac, the logic remains sound. Always use these functions when moving multi-byte integers across a boundary (network or disk).
π Practical Task
Exercise: Building a Cross-Platform Packet Length Parser
You are tasked with writing a function that parses a simulated network packet. The packet starts with a 4-byte "length" field in Big Endian format, followed by the actual data.
Requirements:
- Create a function
uint32_t parse_packet_length(const unsigned char *buffer). - The function must take a pointer to a raw byte array.
- It should reconstruct the 32-bit integer from the first 4 bytes using
ntohlor manual bit-shifting (but you must ensure it works correctly on a Little Endian machine). - In your
mainfunction, create a byte arrayunsigned char packet[] = {0x00, 0x00, 0x00, 0x2A, 'H', 'e', 'l', 'l', 'o'};(where0x2Ais 42 in decimal). - Print the parsed length to verify it equals 42.
There are no comments for now.