Memory Address and Addressing

When you declare a variable in your program, how does the computer "find" it? Why can a 32-bit system only support 4GB of RAM?

This lecture lifts the veil on memory addresses, helping you understand the address space—the fundamental limit that determines how much memory a computer can use.


Everyday analogy: mailboxes in an apartment building

Imagine an apartment building where every unit has its own mailbox, and each mailbox is marked with a unique unit number.

When a courier delivers mail, they only need to know the unit number (address) to accurately drop the letter into the correct mailbox. The way the CPU accesses memory is almost exactly the same as a courier delivering mail:

  • Each memory unit (1 byte)= A Mailbox
  • Memory address= the mailbox's unit number (numbered starting from 0)
  • Address bus width= the maximum number of digits the unit number can have (determines how many numbers can be assigned at most)

If the unit number only has 2 digits (00~99), the building can have at most 100 mailboxes. Similarly, if the address bus is only 32 bits wide, the CPU can distinguish at most 2^32 distinct memory cells.


The Essence of Memory Addresses

An Address Is Just a Number

From a hardware perspective, memory is a long row of physical cells that can store 0s and 1s. Each cell stores 1 byte (8 bits), and each cell has a unique number—this number is theMemory address。

The CPU reads and writes memory through a three-bus system:

  1. The CPU places the target address onto theAddress busOn the address bus—"I want to find mailbox 103"
  2. CPU isControl busOn the control bus, it sends a "read" or "write" signal—"Please give me what's inside" or "Please put this in."
  3. Memory Places Data IntoData busOn the data bus, data is placed on it (read), or taken from the data bus (write).

Why does each address correspond to 1 byte?

This is a design choice. Some early computers used "word addressing" (each address corresponds to 2 or 4 bytes), but modern computers almost all useByte addressing—byte addressing, where each address corresponds to 1 byte.

The benefit: processing characters (each ASCII character takes up 1 byte) is very natural, with no need for extra alignment and splitting logic.

内存地址:  0      1      2      3      4      5      6      7
         +------+------+------+------+------+------+------+------+
内容:    | 0x48 | 0x65 | 0x6C | 0x6C | 0x6F | 0x00 | 0xFF | ... |
         +------+------+------+------+------+------+------+------+
           'H'    'e'    'l'    'l'    'o'    '\0'

一个 32 位整数 0x12345678 在内存中的存储(小端序):
地址:    0      1      2      3
         +------+------+------+------+
内容:    | 0x78 | 0x56 | 0x34 | 0x12 |
         +------+------+------+------+
          低字节在低地址——这就是「小端序」(Little Endian)

Why a 32-bit system can only use at most 4GB of memory

Direct cause: the width of the address bus

A 32-bit address is a binary number made up of 32 0s or 1s.

The number of distinct values that 32 binary bits can represent is:

2^32 = 2 × 2 × 2 × ... (32 times) =4,294,967,296

That is approximately4GB(GigaBytes)。

If each address corresponds to 1 byte, then a 32-bit address space can address a total of 4,294,967,296 bytes = 4 GB.

Derivation

1 KB  = 2^10 字节 = 1,024 字节
1 MB  = 2^20 字节 = 1,048,576 字节
1 GB  = 2^30 字节 = 1,073,741,824 字节

2^32  = 2^2 × 2^30
      = 4 × 1 GB
      = 4 GB

Therefore,32-bit = 4GBThis is not a coincidence, but a strict mathematical relationship between the number of address bits and the addressable space.

Analogy: the number of digits in a phone number

Suppose a city's phone numbers are 8 digits (00000000 ~ 99999999). What is the maximum number of different phone numbers this city can have?

10^8 = 100 million. If the population exceeds 100 million, 8 digits are no longer enough—it must be upgraded to 9 digits.

Same logic: a 32-bit address, pushed to its limit, is also 4GB. When memory requirements exceed 4GB, a 32-bit system is helpless—this is why the industry collectively migrated to 64-bit.


What does 64-bit mean?

The theoretical limit of a 64-bit address space is:

2^64 = 18,446,744,073,709,551,616 bytes ≈16 EB(ExaBytes)

How large is this number?

  • 1 EB = 1 billion GB
  • 16 EB is roughly several hundred times the amount of data traffic generated by the global internet in a single day in 2025.
  • If each byte were a grain of sand, 16 EB of sand could fill about 2,000 standard swimming pools.

Currently, no consumer motherboard supports this much memory. Modern 64-bit CPUs typically implement only 48-bit physical address lines (addressable up to 256 TB), and Windows 10 Home is limited to 128 GB—but for the present and foreseeable future, this is already "good enough."

The true significance of 64-bit is not how much memory you can install, but rather:Software and operating systems no longer need to devise various workarounds for insufficient address space.(such as PAE—Physical Address Extension—from the 32-bit era).


Addressable space comparison across different bit widths

Address bitsNumber of Addressable UnitsAddressable spaceVivid analogyRepresents device/system
8 bits256256 BLength of a Text MessageEarly Microcontrollers
10 bits1,0241 KBOne page of plain textEarly EEPROM
16-bit65,53664 KBA Short StoryIntel 8086, 6502
20-bit1,048,5761 MBA book of medium thicknessIntel 8088 (IBM PC)
24-bit16,777,21616 MBA Lossless Music TrackEarly workstations
32-bit4,294,967,2964 GBAn HD MoviePentium, ARMv7
36 bits68,719,476,73664 GBAll the data on a small NASx86 PAE mode
40 bits1,099,511,627,7761 TBThe full capacity of a consumer-grade SSDEarly ARMv8
48 bits281,474,976,710,656256 TBThe amount of data in a mid-sized data centerModern x86-64
64-bit~1.84 × 10^1916 EBAn astronomical number, far exceeding current physical memory limitsx86-64 / ARMv8 architecture limits

Note: the change from 32-bit to 64-bit is not "doubling", but a jump from 4GB to 16EB—an increase ofOver 4 billion times! The power of exponential growth is fully demonstrated here.


Interactive demo: address space calculator

The program below systematically shows the addressable space corresponding to different address bit widths, and uses visualization to give you an intuitive feel for exponential growth.

Example

"""
Memory address space calculator (example demo)
Function:
1. Calculate the addressable space corresponding to any address bit count
2. Print the complete comparison table from 8-bit to 64-bit
3. Visualize exponential growth
"""

import math

def address_space_to_str(bytes_val):
    """
Convert the byte count to a human-readable format
Supported: B, KB, MB, GB, TB, PB, EB
    """

    if bytes_val == 0:
        return "0 B"

    units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']
    unit_idx = 0
    value = float(bytes_val)

    # Increase units level by level, keeping values within a reasonable range
    while value >= 1024 and unit_idx < len(units) - 1:
        value /= 1024
        unit_idx += 1

    if unit_idx == 0:
        return f"{int(value):,} {units[unit_idx]}"
    elif value >= 100:
        return f"{value:,.0f} {units[unit_idx]}"
    elif value >= 10:
        return f"{value:,.1f} {units[unit_idx]}"
    else:
        return f"{value:,.2f} {units[unit_idx]}"


def print_address_space_table(start_bits=8, end_bits=64):
    """
Print address space comparison table
:param start_bits: starting bit position
:param end_bits: ending bit count
    """

    print("=" * 85)
    print(Memory Address Space Calculator - EXAMPLE Computer Organization Principles)
    print("=" * 85)
    print()
    print(f"{'Address bits':<10} {'can寻址单元number':>20} {'Addressable space':>18} {'relative to 32-bit':>15}")
    print("-" * 85)

    # Based on 32-bit
    baseline_32 = 2 ** 32

    # Select significant digits for display
    key_bits = [8, 10, 12, 14, 16, 18, 20, 24, 28, 30, 32, 36, 40, 44, 48, 52, 56, 60, 64]

    for bits in key_bits:
        if bits < start_bits or bits > end_bits:
            continue

        count = 2 ** bits
        space_str = address_space_to_str(count)

        # Calculate the ratio relative to 32 bits
        if bits <= 32:
            ratio = count / baseline_32
            ratio_str = f"1/{int(baseline_32/count)}" if ratio < 1 else "1x"
        else:
            ratio = count / baseline_32
            if ratio >= 1_000_000_000:
                ratio_str = f{ratio/1_000_000_000:,.1f} hundred million times
            elif ratio >= 1_000_000:
                ratio_str = f{ratio/1_000_000:,.0f} million times
            elif ratio >= 1_000:
                ratio_str = f"{ratio/1_000:,.0f} thousand times"
            else:
                ratio_str = f"{ratio:,.0f}x"

        print(f"{bits} bits{'':<5} {count:>20,}  {space_str:>18}  {ratio_str:>15}")

    print("-" * 85)
    print()


def analyze_why_4gb():
    """Detailed analysis of the derivation process for 32-bit = 4GB"""
    print("=" * 85)
    print("Deep Dive: Why is 2^32 = 4 GB?")
    print("=" * 85)
    print()
    print("Derivation steps:")
    print()
    print("  2^10 = 1,024                             → 1 KB")
    print("  2^20 = 1,024 × 1,024 = 1,048,576         → 1 MB")
    print("  2^30 = 1,024 × 1,024 × 1,024             → 1 GB")
    print(" = 1,073,741,824 bytes")
    print()
    print("  2^32 = 2^2 × 2^30")
    print("       = 4 × 1,073,741,824")
    print(" = 4,294,967,296 bytes")
    print(" = 4 GB (more precisely, 4 GiB)")
    print()
    print("Note: strictly speaking")
    print("    4 GB  (Gigabyte)  = 4 × 10^9   = 4,000,000,000 byte(decimal)")
    print(4 GiB (Gibibyte) = 4 × 2^30 = 4,294,967,296 bytes (binary))
    print(In the computer field, what is commonly called '4GB memory' actually refers to 4 GiB.)
    print()


def visualize_growth():
    """Display exponential growth with a simple ASCII bar chart"""
    print("=" * 85)
    print("Exponential growth visualization (ASCII bar chart, logarithmic scale)")
    print("=" * 85)
    print()

    bits_list = [8, 16, 20, 24, 28, 32, 36, 40, 48, 64]
    # Use logarithm to compress the display range
    for bits in bits_list:
        count = 2 ** bits
        log10 = math.log10(count)
        bar = "=" * int(log10 * 3)  # Each log10 unit = 3 equals signs
        space_str = address_space_to_str(count)
        print(f" {bits:>3} bits |{bar:<60} {space_str}")

    print()
    print(Observation: From 8-bit to 64-bit, the bar length grows steadily (because it is on a logarithmic scale))
    print(" But the actual value grows exponentially—from 32-bit to 64-bit, it increased by more than 4 billion times!")
    print()


def practical_questions():
    """Practical application problems"""
    print("=" * 85)
    print("Practical application: calculate how much memory your system can support.")
    print("=" * 85)
    print()

    questions = [
        ("old-fashioned microcontroller with an 8-bit address bus", 8),
        (6502 CPU with a 16-bit address bus, 16),
        ("laptop with a 32-bit operating system", 32),
        (32-bit server with PAE enabled (36-bit physical address), 36),
        (Modern 64-bit home computer (48-bit physical address), 48),
        ("Theoretical limit of 64-bit architecture", 64),
    ]

    for desc, bits in questions:
        space = 2 ** bits
        print(f"  {desc}:")
        print(f"    2^{bits} = {space:,} byte = {address_space_to_str(space)}")

    print()


# ========== Main Program ==========
if __name__ == "__main__":
    print_address_space_table(8, 64)
    analyze_why_4gb()
    visualize_growth()
    practical_questions()

    # Extra: Interactive Query
    print("=" * 85)
    print("Custom query")
    print("=" * 85)
    print()

    custom_bits = [1, 4, 8, 16, 32, 64, 128]
    for bits in custom_bits:
        space = 2 ** bits
        print(f{bits:>3} bit addresses → {space:,} addresses)
        if bits >= 128:
            print(f"          → {address_space_to_str(space)} (Note:Thisnumberquantityalreadyfarexceedcan观测宇宙inOriginal子totalnumber!)")
example AddressSpaceComputedevice — 滑block + KaTeX formula渲染 + CSS Grid Gridcan视transform

Interactive demo: address space calculator

Drag the slider to adjust the number of address bits (1~64), and use KaTeX to render 2nformula in real time, with CSS Grid drawing the memory cell grid (for 8 bits or fewer, all 256 cells are drawn).

Quick preset button
Address bit slider
8
KaTeX formula rendering area
Total number of addressable units
CSS Grid visualization area
Memory unit grid visualization (each cell represents 1 byte)

PAE: the "life-extension" solution for 32-bit systems

Before 64-bit became widespread, faced with the 4GB limit, Intel designed a workaround—PAE (Physical Address Extension)。

PAE extends the physical address lines from 32 bits to 36 bits, allowing 32-bit CPUs to address up to 2^36 = 64 GB of physical memory.

But here there is a key limitation:Each process's virtual address space is still 32 bits (4GB)A single program is still limited to at most 4GB of memory. The benefit of PAE is that it lets servers run more programs concurrently, each program using its own 4GB.

PAE was just a transitional solution; the real solution was to fully switch to 64-bit.


Virtual address vs. physical address

The "address" we discussed earlier mainly refers toPhysical address—the real, hardware-level numbering on the memory modules.

But in modern operating systems, the address your program sees is not a physical address, but aVirtual address。

Why do we need virtual addresses?

  1. Isolation protectionvirtual address: Program A cannot read or write Program B's memory (otherwise a crashing program could corrupt the entire system).
  2. Simplify programmingEach program thinks it exclusively owns the entire address space (e.g., 0 to 4GB), without needing to care about which addresses other programs use.
  3. Flexible memory managementThe operating system can temporarily write infrequently used memory to disk (swap/paging) and read it back when needed.

Inside the CPU, there is a hardware unit calledMMU (Memory Management Unit), which is responsible for translating virtual addresses into physical addresses in real time.

程序看到的虚拟地址 → [MMU 翻译] → 物理地址 → [地址总线] → 内存条

程序写:  *(0x08048000) = 42
         ↓
MMU 查表: 虚拟页 0x08048 → 物理页 0x1A3F0
         ↓
物理地址: 0x1A3F0000 的内存单元被写入 42

Virtual addresses are the cornerstone of modern operating system stability. Without virtual addresses and the MMU, a program reading or writing out of bounds could blue-screen the entire system—a nightmare that happened every day in the DOS era.


How memory addresses manifest in programming

In C/C++, you can directly manipulate memory addresses (pointers). Here is an intuitive example:

Example

"""
Viewing memory addresses in Python (example demo)
Although Python abstracts away the underlying pointers, the id() function still gives us a glimpse into the concept of addresses.
"""

import sys

# Memory Addresses of Basic Types
a = 42
b = 42
c = 999
d = 999

print(EXAMPLE Memory Address Exploration: Python id() Demonstration)
print("=" * 60)
print()

# Python caches small integers (-5~256); variables with the same value point to the same address
print("Small integer caching phenomenon:")
print(fa = 42, id(a) = {id(a)} (hexadecimal: 0x{id(a):X}))
print(fb = 42, id(b) = {id(b)} (hexadecimal: 0x{id(b):X}))
print(f" a and b point to the same object: {a is b}")
print()

print("Large integers are not cached:")
print(fc = 999, id(c) = {id(c)} (hexadecimal: 0x{id(c):X}))
print(fd = 999, id(d) = {id(d)} (hexadecimal: 0x{id(d):X}))
print(fc and d refer to the same object: {c is d} (usually False, depending on Python implementation))
print()

# Address of the string
text = "Hello EXAMPLE"
print(fThe id of string 'Hello EXAMPLE': 0x{id(text):X})

# Address of the list and its element addresses
lst = [10, 20, 30]
print(f"\nlist lst = {lst}")
print(f" The id of the list itself: 0x{id(lst):X}")
print(flst[0]'s id: 0x{id(lst[0]):X})
print(flst[1]'s id: 0x{id(lst[1]):X})
print(flst[2]'s id: 0x{id(lst[2]):X})

print()
print(Description:)
print(Although Python does not expose physical memory addresses, id() returns the unique identifier of the object.)
print("In the CPython implementation, id() returns the address of the object in virtual memory.")
print(You can see that list elements are stored scattered (not contiguous like C arrays).)
print()

# The Embodiment of 32-bit vs 64-bit
print("=" * 60)
print("Python interpreter bitness information:")
print(f"  sys.maxsize = {sys.maxsize}")
print(f"  2^31 - 1     = {2**31 - 1}")
print(f"  2^63 - 1     = {2**63 - 1}")
print()
if sys.maxsize > 2**32:
    print(fYour Python is 64-bit (maxsize > 2^32))
else:
    print(f" Your Python is 32-bit (maxsize <= 2^32)")
other extensions