Skip to Content
Course content

211: Endianness and Byte Order Handling

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 ntohl or manual bit-shifting (but you must ensure it works correctly on a Little Endian machine).
  • In your main function, create a byte array unsigned char packet[] = {0x00, 0x00, 0x00, 0x2A, 'H', 'e', 'l', 'l', 'o'}; (where 0x2A is 42 in decimal).
  • Print the parsed length to verify it equals 42.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.