Fundamentals 6 min read

How Linkers Resolve Virtual Addresses Before a Program Runs

This article explains why C/C++ function and global variable addresses are fixed at link time, how modern operating systems provide each process with a private virtual address space, the role of ABI and linker scripts in defining segment layout, and how the loader later maps those virtual addresses to physical memory.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How Linkers Resolve Virtual Addresses Before a Program Runs

Why Addresses Are Fixed at Link Time

In C/C++ programs you can print the address of a function or global variable, and those addresses are already determined during compilation, specifically at the linking stage, assuming address‑space layout randomization (ASLR) is not considered.

The common misconception is that the linker decides on a physical memory address; in reality it works with virtual addresses provided by the operating system.

Virtual Address Space

Modern operating systems give each process its own private, continuous virtual address space (VAS) ranging from 0 up to the maximum address (e.g., 0xFFFFFFFF on 32‑bit systems). This space appears to the process as if it owns the entire memory, allowing code and data to be placed at fixed virtual addresses.

Because the VAS layout is stable for the lifetime of the process, the linker can rely on this consistency when assigning addresses.

ABI and Executable File Format Conventions

The operating system also defines a default load base for executables. On Linux (ELF) the traditional default base address is 0x400000 (also used for 64‑bit binaries).

When the linker merges object files, it follows a linker script that specifies the layout of sections. The script can be the toolchain’s default (e.g., GNU ld) or a custom one provided by the programmer.

SECTIONS {
    . = 0x400000;       /* Base address */
    .text : { *(.text) } /* .text follows base */
    . = ALIGN(4096);     /* Align to 4 KB */
    .data : { *(.data) } /* .data follows */
    .bss  : { *(.bss) }  /* .bss at the end */
}

The script defines:

The order of sections such as .text, .data, .bss, .rodata, etc.

The starting virtual memory address (VMA) for each section.

The alignment between sections (e.g., . = ALIGN(4096)).

The linker then calculates the final virtual addresses for all code and data based on these rules and writes the information into the ELF executable.

From Virtual to Physical

At runtime, the loader maps the ELF file into the process’s virtual address space as defined by the linker. The operating system later assigns actual physical memory pages to those virtual addresses, typically on first access, so the true physical address is only known after the program starts executing.

Programs do not need to know their physical addresses; they operate entirely within the virtual address space.

This separation of responsibilities—linker assigning virtual layout, OS providing a stable VAS, and hardware (MMU) handling the virtual‑to‑physical translation—allows modern systems to determine a program’s memory layout before it runs while keeping the physical address details abstracted away.

ELFC++Linkerlinker-scriptaddress space
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.