C Basics · beginner · ~15 min
Understand how multi-byte values are laid out in memory and on the wire.
Endianness is the byte order in which a multi-byte integer is stored in memory or transmitted on the wire. Little-endian means least-significant byte first; big-endian means most-significant first. Almost every networking, file-format, and reverse-engineering bug eventually involves an endianness mismatch.
C exposes raw bytes through pointers. Whenever your bytes leave the process — to a socket, a binary file, a memory-mapped device — endianness matters. Get it wrong and the receiver decodes garbage.
Host byte order vs network byte order. Most modern CPUs are little-endian (x86, ARM in default mode). Network byte order is big-endian. The POSIX helpers htons, htonl, ntohs, ntohl convert.
Manual byte assembly. (buf[0] << 8) | buf[1] reads a 16-bit big-endian value from a byte buffer. Equivalent for 32-bit needs 4 shifts.
Pentester mindset. Reverse engineering a captured binary or protocol always starts with: which endianness? Tools like xxd show the bytes; you map them to the integer interpretation yourself.
Defensive coding habit. Read multi-byte fields from untrusted buffers byte-by-byte with explicit shifts. Never cast (uint32_t*)buf and dereference — alignment UB.
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); /* host -> network (big endian) */
uint16_t htons(uint16_t hostshort); /* same, 16-bit */
uint32_t ntohl(uint32_t netlong); /* reverse */
uint16_t ntohs(uint16_t netshort);
On x86 and ARM, the least significant byte of a 32-bit value comes first in memory (little-endian). On the network, the most significant byte comes first (big-endian). Whenever you cross that boundary — writing to a socket, reading a binary file format, parsing a packet — you have to convert.
#include <arpa/inet.h>
uint32_t host = 0x12345678;
uint32_t net = htonl(host); /* swap on little-endian; no-op on big */
uint32_t read_u32_be(const unsigned char *b){
return ((uint32_t)b[0] << 24)
| ((uint32_t)b[1] << 16)
| ((uint32_t)b[2] << 8)
| (uint32_t)b[3];
}
xxd file.bin | head shows the raw bytes; compare against your decoded integer to confirm byte order. printf("%08x", v) for hex inspection.
Always read multi-byte fields one byte at a time with shifts. Casting unaligned unsigned char * to uint32_t * and dereferencing is undefined on strict-alignment platforms.
TCP/IP headers, PNG/PE/ELF file formats, USB descriptors, embedded device protocols, every byte that crosses a network or storage boundary.
bswap32. 2. Read a 16-bit big-endian length from a buffer. 3. Detect your host endianness at runtime by inspecting (uint32_t)1.Host byte order is little-endian on almost every machine you'll touch; network byte order is big-endian. Use htons/htonl on the way out, ntohs/ntohl on the way in. For binary files and packets, prefer byte-by-byte reads with explicit shifts.