In this lesson, you will learn how the linker bridges the gap between isolated translation units to create a cohesive memory map for your embedded system.
When you compile individual .c files, the compiler creates "Relocatable Object Files." These files are self-contained but incomplete; they contain functions and variables, but they don't know the absolute addresses of code or data residing in other files. This is where the linker steps in to perform "Symbol Resolution."
The Symbol Resolution Process
Think of an object file as a house under construction. The file knows it needs a "plumbing" service, but it doesn't know where the city’s main supply line is located. In your code, if main.o calls initialize_sensor(), it places a placeholder in its symbol table: "I need the address of initialize_sensor."
The linker performs two primary steps:
- Symbol Matching: It scans all object files and libraries, matching "undefined" symbols (calls to functions) with their corresponding "definitions" (the actual function code).
- Relocation: Once it finds the definition, it calculates the absolute memory address where that function will eventually reside in the final binary (based on your linker script) and updates every reference in the machine code to point to that specific address.
A Concrete Illustration
Imagine main.c has a reference to sensor_read():
// main.c
extern int sensor_read(void); // Undefined symbol
int val = sensor_read(); // Linker needs to patch this call
The compiler produces an object file with a "Relocation Entry" stating: “I don't know where sensor_read is; fill in this address later.”
During linking, if sensor.o provides the definition of sensor_read() at address 0x08000150, the linker performs the following:
- Locates the placeholder in
main.o. - Overwrites the placeholder with the constant value
0x08000150. - Collapses all individual files into a single, contiguous executable image ready for flash memory.
If the linker finds no definition for a symbol, or if it finds two files attempting to define the same symbol (Multiple Definition Error), the process fails, preventing runtime crashes.
Key Takeaway: The linker acts as a master map-maker, replacing abstract symbol placeholders with finalized, absolute memory addresses so that disparate pieces of code can communicate seamlessly at runtime.