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

  • Forgetting to convert endianness when reading/writing network protocol fields, leading to wildly incorrect values (e.g., a port number displayed as 13330 instead of 80).
  • Mixing sign-extension and zero-extension when widening smaller multi-byte types, corrupting arithmetic on negative values.
  • Assuming ARM is always little-endian in every context — while overwhelmingly true for modern general-purpose OSes, the architecture does support big-endian modes in specialized configurations.

Best Practices

  • Always explicitly document and, where necessary, convert endianness at protocol or file-format boundaries.
  • Use the architecture’s dedicated byte-swap instruction (BSWAP, REV) rather than manual shifting/masking for both clarity and performance.
  • Double-check sign vs. zero extension whenever widening a smaller data type, especially when the source value could be negative.

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

  • Multi-byte data types (words, doublewords, quadwords) are built from sequences of individually addressable bytes, and their internal byte order is defined by endianness.
  • x86/x86-64 is strictly little-endian; ARM defaults to little-endian in essentially all modern deployments but technically supports big-endian too.
  • Sign extension vs. zero extension matters when widening smaller types into larger registers.
  • Byte-swap instructions (BSWAP on x86, REV on ARM) exist specifically to handle cross-endian data efficiently.
  • Understanding this topic is essential for network programming, file format parsing, and cross-platform debugging.

References

  • Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1 (data types and byte ordering)
  • Arm® Architecture Reference Manual for A-profile architecture (endianness and data types chapter)
  • IETF RFC 1700 and related networking standards documentation on network byte order
  • GNU Assembler (GAS) documentation on data directives (.byte, .hword, .word, .quad)
Total
0
Shares

Leave a Reply

Previous Post
What is the role of the carry flag in Assembly language

What is the role of the carry flag in Assembly language

Next Post
Explain the concept of privilege levels in Assembly language programming

Explaining the Concept of Privilege Levels in Assembly Language Programming

Related Posts