Foundationsword sizebyteportabilitypointer sizeISA

Bits, Bytes and Words — and Why "Word" Is Not a Fixed Size

The byte is nearly universal. The word is not: it means whatever a given architecture, compiler or document says it means, and the confusion this causes is responsible for a surprising share of portability bugs.

Follow the mechanism

Software view, hardware view

The gap between what you wrote and what the machine does is where this whole domain lives.

The question
What is a "word", why does its size keep changing depending on who is talking, and what actually depends on it?
What you wrote
An `int` is 32 bits, a pointer is 64 bits on a modern machine, and a "word" is whatever the CPU handles naturally. These feel like fixed facts about computing.
What the hardware does
The byte — eight bits — is effectively universal on contemporary hardware. Everything above it is a convention. Word size means register width on one architecture, the natural operand size on another, and 16 bits in Windows API documentation for historical reasons that outlived the hardware.
Code that assumes a size rather than asking for one breaks when it moves: between architectures, between operating systems, between 32-bit and 64-bit builds of the same program. The bugs are quiet — a truncated pointer, a struct that no longer matches a file format — and they surface far from the assumption that caused them.
SourceCompilerInstructionsFront EndExecutionRegistersCachesMemoryI/OBehavior

The ladder, and where it stops being universal

ISA-SPECIFICWord size is defined per architecture, and the x86 assembly meanings of `word`/`dword`/`qword` are frozen at their historical sizes rather than tracking register width. AArch64 and RISC-V documentation use the term differently again.

A bit is one binary digit. A byte is eight bits and is the smallest individually addressable unit on essentially every architecture you will meet — memory addresses count bytes, not bits, which is why extracting a single bit requires a shift and a mask rather than an address. Historically byte sizes other than eight existed; they are of no practical concern now, but the term "octet" survives in networking specifications precisely because those authors wanted no ambiguity.

Above the byte, universality ends. A word is a term with an architecture-relative meaning: informally the natural unit the processor operates on, which usually corresponds to register width and to the size of a pointer. On a 64-bit architecture that is 64 bits. But the term is also frozen into documentation and instruction mnemonics at whatever size was natural when they were written — in x86 assembly, word still means 16 bits, dword 32 and qword 64, decades after the natural width moved on.

The practical consequence is that "word" is never a safe thing to assume in code. What you can rely on is what the language guarantees, and languages differ sharply on how much they guarantee: some fix every width exactly, others promise only minimum ranges and let the platform decide.

Sizes that are guaranteed, and sizes that are not
QuantityFixed?What actually determines it
BitYesOne binary digit, by definition
ByteEffectively yesEight bits on all contemporary hardware; called an octet in network standards to remove doubt
Machine wordNoArchitecture-defined; usually register and pointer width, but the term is overloaded
int in C or C++NoImplementation-defined; at least 16 bits, in practice 32 on most 64-bit platforms
int in Java or C#YesExactly 32 bits, fixed by the language specification
PointerNoAddress width of the platform; commonly 64 bits, but 32-bit builds and embedded targets differ
int32_t, uint64_tYesFixed-width types that exist precisely so you can stop guessing

What a 64-bit word actually looks like in memory

Addressing counts bytes, so a 64-bit value occupies eight consecutive addresses. The layout below shows why two separate concerns arise: the order those bytes appear in, and whether the value's starting address is a multiple of its size.

Byte order is a platform decision covered in Endianness: Which Byte Comes First — it matters the moment bytes leave your process, in a file or on a network. Alignment is a hardware preference covered in Alignment: Why Addresses Are Not Arbitrary: a 64-bit value starting at an address divisible by eight can be loaded in a single access, whereas one straddling a boundary may require two, and on some architectures faults outright.

Both concerns come from the same root fact: memory is a flat sequence of bytes, and any structure above that is a convention which the hardware partially enforces and partially just prefers.

A 64-bit value occupying eight consecutive byte addresses within one cache line
usedanother thread's dataSIMPLIFIED
rest of the 64-byte line
line 0
64 bytes total1 cache line touched

The eight bytes are one value, but the hardware moves the whole surrounding line between memory and cache — see Memory Moves in Lines, Not Variables. The line size shown here is a common one, not a universal constant.

What actually depends on the width

Several things scale with word size at once, which is why the transition from 32-bit to 64-bit platforms changed more than the maximum integer. Address space is the headline: 32 bits of address covers roughly four billion bytes, which is why 32-bit processes hit a hard ceiling around 4 GB regardless of installed memory. Pointer size doubles, which enlarges every pointer-heavy data structure and, unhelpfully, reduces how many nodes fit in a cache line — a real cost for pointer-based structures.

Register width determines how much a single instruction moves or operates on, so wider registers mean fewer instructions for bulk work — an effect SIMD: One Instruction, Many Elements pushes much further with dedicated vector registers. And the natural atomic width — the largest value the hardware can update indivisibly without a lock — usually tracks word size, which matters directly for Atomic Instructions: What the Hardware Actually Guarantees.

The defensive habit is simple: never encode a size assumption you did not have to. Use fixed-width types when a layout must match a file, a protocol or another process; use the language's size-of operator rather than a literal; and never assume a pointer fits in an integer type unless the language guarantees it.

Assumes sizes — breaks on a different platform or build
1// Assumes a pointer fits in 4 bytes and int is exactly 4 bytes.
2// Both assumptions are false on common 64-bit targets.
3const HEADER_BYTES = 12 // "2 ints and a pointer"
4buffer.writeInt32(offset, ptr) // truncates a 64-bit pointer
5
6// Assumes the struct is exactly as wide as the sum of its fields,
7// which ignores padding the compiler inserts for alignment.
8const RECORD_BYTES = 1 + 4 + 8 // = 13, but sizeof() will say 16
States sizes explicitly — portable and checkable
1// Fixed-width types where the layout must match something external.
2const HEADER_BYTES = 2 * 4 + POINTER_BYTES // derived, not assumed
3buffer.writeBigUint64(offset, ptr) // full width
4
5// Ask the platform rather than computing the sum yourself:
6// the answer accounts for padding and alignment.
7const RECORD_BYTES = sizeOf(Record) // 16, correctly

Neither version is faster; the difference is that one of them keeps working. Size assumptions fail silently — a truncated pointer produces a wrong address rather than an error, and a struct size computed by hand diverges from the real layout as soon as padding is involved (Padding: Why Your Struct Is Bigger Than Its Fields).

Key points

  • The byte is eight bits and is the smallest addressable unit; bit-level access requires shifting and masking.
  • "Word" has no universal size — it is architecture-relative, and frozen at historical sizes in some assembly and API documentation.
  • Language guarantees vary: some fix widths exactly, others promise only a minimum range.
  • Word size simultaneously drives address space, pointer size, register width and natural atomic width.
  • Use fixed-width types and size-of operators for anything whose layout is externally visible.

Follow the mechanism

The path through the machine, hop by hop — and the conclusions it invites that are wrong.

  1. 1
    Address → memory: addresses index bytes, so an N-byte value occupies N consecutive addresses.
  2. 2
    Load instruction → register: the instruction encodes an operand width, so the same address can be read as one byte or eight.
  3. 3
    Register width → ISA: the architecture fixes how wide a general-purpose register is, which is what most definitions of "word" track.
  4. 4
    Address width → address space: an N-bit address can name at most 2^N bytes, which is the hard ceiling on a 32-bit process.
  5. 5
    Compiler → layout: the compiler chooses concrete sizes and inserts padding, so a struct is usually larger than the sum of its fields.
What people conclude from this — wrongly
  • Assuming int is 32 bits everywhere; the C and C++ standards guarantee only a minimum range.
  • Assuming a struct's size equals the sum of its fields, which ignores alignment padding.
  • Reading "word" in one document with the meaning it has in another — x86 assembly and general architecture discussion disagree.
  • Assuming a pointer safely fits into an integer type; on 64-bit targets a 32-bit integer will silently truncate it.

Consequences, controls and cost

What it causes
  • • 32-bit processes cannot address more than about 4 GB regardless of physical memory installed.
  • • Moving to 64 bits doubles pointer size, enlarging pointer-heavy structures and fitting fewer nodes per cache line.
  • • Code with baked-in size assumptions breaks when rebuilt for a different target, usually silently and far from the assumption.
  • • Binary formats and network protocols must state widths explicitly or become unportable by construction.
What you can do
  • • Use fixed-width types (`int32_t`, `uint64_t` and equivalents) wherever the layout is externally visible.
  • • Derive sizes with the language's size-of operator instead of hand-computing them from field widths.
  • • Enable warnings for implicit narrowing conversions; most size bugs are a truncation the compiler could have flagged.
  • • Test on more than one target when portability matters — a 32-bit build surfaces these bugs immediately.
How to see it
  • • Print the size-of every type in a layout you care about rather than reasoning about it.
  • • Add compile-time assertions on struct sizes and field offsets so a layout change fails the build instead of corrupting data.
  • • Build for a 32-bit target as a portability check even if you never ship it.
What it costs
  • • Fixed-width types can be slower than the platform's natural width on architectures that must emulate the requested size.
  • • Wider pointers cost memory and cache footprint in pointer-heavy structures, which is a real performance loss.
  • • Explicit sizes everywhere is more verbose than relying on defaults, and only pays for itself where layout is externally visible.

Scope

§224 — what these claims are specific to.

What these claims are specific to
  • ISA-SPECIFICRegister width and pointer width are architecture properties. x86-64 and AArch64 are both 64-bit but differ in register count and in how sub-width operations behave.
  • ABI-SPECIFICWhether long is 32 or 64 bits is decided by the ABI, not the ISA: LP64 on Linux and macOS makes it 64 bits, LLP64 on Windows keeps it 32 on the same hardware.
  • PLATFORM-SPECIFICStruct sizes depend on the compiler's alignment rules and any packing directives, so identical source can produce different layouts on different toolchains.

Misconceptions

Claim
“A word is 32 bits.”
Reality
Only on a 32-bit architecture, and even then the term is used inconsistently. In x86 assembly word means 16 bits regardless of the machine, because the mnemonic was fixed when that was the natural width.
Claim
“A 64-bit CPU makes programs faster because it processes twice as much data.”
Reality
It widens registers and the address space. Most programs are not limited by 32-bit arithmetic, and the doubled pointer size can make pointer-heavy structures measurably slower by consuming more cache.
Claim
“The size of a struct is the sum of its field sizes.”
Reality
The compiler inserts padding so each field meets its alignment requirement, so the total is usually larger and depends on field order — see Padding: Why Your Struct Is Bigger Than Its Fields.

Apply it