How Are Multi-Byte Data Types Represented in Assembly Language?

How are multi-byte data types represented in Assembly language

I still remember the exact bug that taught me to respect byte ordering: a network packet parser I wrote worked flawlessly on my x86 development machine and produced complete garbage the moment I ran it against data captured from a different kind of device. The values weren’t wrong — they were just stored in a different byte order than I expected. That bug is what pushed me to really understand how multi-byte data types are represented at the assembly level, and it’s a topic that quietly underlies almost everything from network programming to file formats to cross-platform debugging.

What Is a Multi-Byte Data Type?

A multi-byte data type is any value that requires more than a single byte (8 bits) of storage — words (16 bits), doublewords (32 bits), quadwords (64 bits), and beyond. Since memory is fundamentally addressed byte-by-byte, the CPU and its instruction set must define a consistent convention for exactly which byte goes where when a multi-byte value is stored or loaded.

Naming Conventions Across Architectures

Size (bits)x86/x86-64 NameARM NameC Equivalent
8ByteBytechar
16WordHalfwordshort
32Doubleword (dword)Wordint
64Quadword (qword)Doublewordlong long / pointer (64-bit)
128Double quadword (dqword) / XMM registerQuadword (Q register, SIMD)__int128 / SIMD vector

Notice that x86 calls a 32-bit value a “doubleword” while ARM calls it simply a “word” — the same bit-width has different names depending on which architecture’s manual you’re reading, which trips up a lot of newcomers moving between the two.

Endianness: The Core Concept

The single most important idea in multi-byte representation is endianness — the order in which the individual bytes of a multi-byte value are stored in memory.

Little-Endian (x86, x86-64, and ARM in its default mode)

The least significant byte is stored at the lowest memory address.

Big-Endian (network byte order, and ARM when configured for it)

The most significant byte is stored at the lowest memory address.

Let’s represent the 32-bit value 0x12345678 in memory, starting at address 0x1000:

AddressLittle-Endian ByteBig-Endian Byte
0x10000x780x12
0x10010x560x34
0x10020x340x56
0x10030x120x78
flowchart LR
    subgraph LittleEndian["Little-Endian Memory Layout (0x12345678)"]
        A1["Addr 0x1000: 0x78 (LSB)"] --> A2["Addr 0x1001: 0x56"]
        A2 --> A3["Addr 0x1002: 0x34"]
        A3 --> A4["Addr 0x1003: 0x12 (MSB)"]
    end
    subgraph BigEndian["Big-Endian Memory Layout (0x12345678)"]
        B1["Addr 0x1000: 0x12 (MSB)"] --> B2["Addr 0x1001: 0x34"]
        B2 --> B3["Addr 0x1002: 0x56"]
        B3 --> B4["Addr 0x1003: 0x78 (LSB)"]
    end

x86 and x86-64 are strictly little-endian. ARM is bi-endian — it defaults to little-endian in virtually all modern operating systems (Linux, Android, iOS, Windows on ARM), but the architecture technically supports a configurable big-endian mode as well.

Assembly Examples: Declaring and Accessing Multi-Byte Data

x86-64 (NASM Syntax)

section .data
    my_byte     db  0x7F                ; 1 byte
    my_word     dw  0x1234              ; 2 bytes
    my_dword    dd  0x12345678          ; 4 bytes
    my_qword    dq  0x123456789ABCDEF0  ; 8 bytes

section .text
    mov     al, [my_byte]        ; load 1 byte into AL
    mov     ax, [my_word]        ; load 2 bytes into AX
    mov     eax, [my_dword]      ; load 4 bytes into EAX
    mov     rax, [my_qword]      ; load 8 bytes into RAX

ARM64 (GAS Syntax)

.section .data
my_byte:    .byte   0x7F
my_half:    .hword  0x1234
my_word:    .word   0x12345678
my_dword:   .quad   0x123456789ABCDEF0

.section .text
    ldrb    w0, my_byte           ; load 1 byte, zero-extended into W0
    ldrh    w0, my_half           ; load 2 bytes (halfword)
    ldr     w0, my_word           ; load 4 bytes (word)
    ldr     x0, my_dword          ; load 8 bytes (doubleword)

Sign Extension vs. Zero Extension

When loading a smaller multi-byte value into a larger register, the CPU must decide what to fill the extra bits with — this is where sign vs. zero extension matters.

x86-64

movzx   eax, byte [my_byte]     ; zero-extend: fills upper bits with 0
movsx   eax, byte [my_byte]     ; sign-extend: fills upper bits with the sign bit

ARM64

LDRB    W0, [X1]                ; load byte, zero-extended (implicit for LDRB)
LDRSB   W0, [X1]                ; load byte, sign-extended

Getting this wrong is a classic source of bugs — treating an unsigned byte as signed (or vice versa) silently corrupts arithmetic once the value is extended into a larger register.

Internal Working: Loading a Multi-Byte Value

sequenceDiagram
    participant CPU as CPU Execution Unit
    participant Bus as Memory Bus
    participant Mem as Main Memory (byte-addressable)

    CPU->>Bus: Request N bytes starting at address X
    Bus->>Mem: Read bytes X, X+1, ... X+N-1
    Mem-->>Bus: Return raw bytes
    Bus-->>CPU: Deliver bytes according to endianness rule
    CPU->>CPU: Assemble bytes into register value (LSB-first or MSB-first)

The key insight is that memory itself has no concept of “words” or “endianness” — it’s just an array of individually addressable bytes. Endianness is purely a convention the CPU (and the software reading raw memory) applies when interpreting a sequence of bytes as a single multi-byte number.

Practical Use Cases

  1. Network programming: Network protocols (TCP/IP) conventionally use big-endian (“network byte order”), while x86 hosts are little-endian — requiring explicit byte-swapping (htons/ntohs/htonl/ntohl in C, or BSWAP in x86 assembly) before sending/receiving multi-byte fields.
  2. File format parsing: Formats like PNG use big-endian fields, while formats like BMP use little-endian — assembly-level or low-level parsers must respect the specific format’s declared endianness.
  3. Cross-platform binary data exchange: Serializing data structures between a little-endian x86-64 server and a big-endian legacy system (or vice versa) requires deliberate conversion.
  4. Reverse engineering: Recognizing byte-swap instructions (BSWAP, REV on ARM) in disassembly is a strong hint that the code is handling network or cross-endian data.

Byte-Swapping Instructions

x86-64

mov     eax, 0x12345678
bswap   eax               ; eax becomes 0x78563412

ARM64

MOV     W0, #0x5678
MOVK    W0, #0x1234, LSL #16   ; W0 = 0x12345678
REV     W0, W0                 ; W0 becomes 0x78563412

Both BSWAP and REV exist specifically because byte-order conversion is such a common operation that hardware vendors provide a single dedicated instruction rather than forcing programmers to shift and mask manually.

Comparison: Endianness Trade-offs

AspectLittle-EndianBig-Endian
Human readability in memory dumpsLess intuitive (least significant byte first)More intuitive (reads left-to-right like written numbers)
Casting between sizesSimpler — reading a smaller type from the start address of a larger one requires no offsetRequires offset adjustment when reinterpreting a smaller type from a larger one
Historical prevalenceDominant in modern desktop, mobile, and server CPUs (x86, x86-64, most ARM deployments)Common in network protocols and some legacy/embedded systems

Debugging Multi-Byte Data

In GDB, you can inspect raw memory bytes and manually verify endianness assumptions:

(gdb) x/4xb &my_dword
0x404020:  0x78  0x56  0x34  0x12
(gdb) p/x my_dword
$1 = 0x12345678

The raw byte dump shows the little-endian storage order, while GDB’s p/x command correctly reassembles and displays the logical value — a good sanity check when debugging suspected endianness mismatches.

Common Mistakes

Best Practices

FAQs

Why is x86 little-endian? It traces back to design decisions in Intel’s early processors; little-endian simplifies certain hardware operations like incrementing a multi-byte counter starting from its lowest-address byte.

Is ARM little-endian or big-endian? ARM is bi-endian at the architecture level, but virtually all modern operating systems (Linux, Android, iOS, Windows on ARM) run it in little-endian mode by convention and default configuration.

Does endianness affect floating-point numbers too? Yes — floating-point values are also multi-byte and follow the same endianness convention as integers on a given architecture.

Summary and Key Takeaways

References

Exit mobile version