5.3.2 · D5Build Systems & Toolchain

Question bank — Object files — .o - .obj, symbol table, relocation entries

1,455 words7 min readBack to topic

For the full machinery these questions probe, see Object files — .o - .obj, symbol table, relocation entries. When a question leans on a neighbouring idea, the link is given inline.


True or false — justify

An .o file contains real, final machine code for your functions.
False. It contains real opcodes but with placeholder address bytes — any call or global reference is a zero waiting to be patched via a relocation, so it is machine code with holes, not finished code.
A .bss section takes up as many bytes on disk as the variables it holds.
False. .bss stores only a size number, not the zeros themselves; the OS zero-fills that region at load time, so int big[1000000]; adds ~0 bytes to the file. See Sections — .text .data .bss .rodata.
Every symbol in a .o is either something the file provides or something it needs.
Mostly true, but incomplete. Beyond DEFINED vs UND there are also LOCAL symbols (file-private labels) and WEAK symbols (defined but overridable), so "provide/need" is the global-binding view, not the whole table.
A relocation entry and a symbol-table entry are the same kind of record.
False. A symbol entry names who a location is; a relocation entry names which bytes to patch and how — it merely references a symbol by index. One symbol can have many relocations pointing at it.
If two .o files both list printf as UND, that is an error.
False. Many files may reference the same undefined symbol; the linker resolves all of them against the single definition (in libc). Two definitions would be the error, not two references.
nm printing U printf means your program is broken.
False. U in a lone .o is normal — it just means "not defined here." It only becomes a problem if no linked object or library ever supplies a definition.
The addend in a PC-relative relocation is arbitrary padding.
False. The addend (typically -4) is precisely the negative distance from the patch slot to the next-instruction address, so the stored displacement lands the branch exactly on the symbol. See PC-relative addressing in x86-64.
Renaming main.o to a.out (or .exe) produces a runnable program.
False. There is no entry point (_start), library symbols are unresolved, and relocations are unpatched; jumping into it crashes. Only the Linker — symbol resolution and relocation step makes it executable.
.o (ELF) and .obj (COFF) are the same file format with a different extension.
False. They are different container formats — ELF and COFF file formats differ in headers, section layout, and relocation-type encodings — though both play the same role in the pipeline.
An absolute relocation and a PC-relative relocation would write the same bytes for the same symbol.
False. Absolute writes (the address itself); PC-relative writes (a distance). They differ by , the location of the patch slot, so only in the impossible case would they coincide.

Spot the error

"undefined reference to add — I must have forgotten to #include the header."
Error: that confuses compile-time and link-time. A missing header is a compiler error (no declaration); undefined reference is a linker error meaning the declaration existed but no .o/library defined the symbol. Fix by adding the source file or -l library.
"int x; at file scope in a.c and b.c just makes two independent locals."
Error: file-scope int x; is a global tentative definition, so you have two globals with the same name — a multiple-definition link error (or silent "common" merge, a classic bug). Use static for file-local or one extern to share.
"The linker patches the code inside the compiler, before the .o is written."
Error: the .o is emitted by the assembler with placeholders still in place; patching happens later, in the linker, once every section has a final address. That ordering is exactly why relocations must exist.
"A relocation stores the final address, so the linker just copies it in."
Error: the relocation stores a type and a symbol reference, not the address — the linker computes the value with the type's formula ( or ) using addresses it chooses at link time.
"R_X86_64_PC32 uses formula ."
Error: PC32 is PC-relative, so its formula is . Dropping the would make the call jump to an address that is off by the patch-slot's own position.
"Marking a symbol WEAK means the linker ignores it."
Error: a weak symbol is used if no strong definition exists; it is only overridden when a strong (GLOBAL) definition of the same name appears. It is a fallback, not an ignore flag.

Why questions

Why does the compiler leave placeholder bytes instead of guessing an address?
Because it compiles one file in isolation and cannot know where other .o files and libraries will be laid out; guessing would break the moment layout changed, so it defers to the linker via relocations — enabling separate compilation.
Why is the addend -4 for a 4-byte call displacement?
The CPU computes the target as next-instruction-address + displacement, and the 4 displacement bytes sit between the patch slot and that next address; -4 cancels that gap so points exactly at the target.
Why does C++ need Name mangling in C++ before symbols even reach the object file?
Because overloading lets many functions share a source name (f(int), f(double)), but a symbol table needs unique names; mangling encodes the signature into the symbol so the linker can tell them apart.
Why prefer PC-relative relocations over absolute ones for code?
PC-relative displacements stay valid when the whole section is moved as a block, which makes position-independent code and shared libraries possible — see Static vs Dynamic Linking.
Why does changing one .c file not force recompiling the whole project?
Because each file compiles to an independent .o with a self-describing symbol/relocation to-do list; only the changed .o is rebuilt and the project is re-linked, which is the whole economic point of object files.

Edge cases

What relocations does a purely local static function that's never address-taken need?
Possibly none — if all its calls are within the same section and resolved by the assembler, no linker patch is required; only cross-section or external references generate relocation entries.
What is the "value" field of an UND symbol?
Zero (or undefined), because the file provides no bytes for it — the value only becomes meaningful once the linker resolves the symbol against a defining object.
Can a symbol be both defined and referenced within the same .o?
Yes — a function that calls itself, or a global used in the same file that declares it, appears once as DEFINED while relocations reference that same symbol index; definition and reference are independent facts.
What happens at link time if two .o files each define a GLOBAL foo?
A multiple-definition error, because the linker must match each reference to exactly one definition; two strong definitions of the same name are ambiguous and rejected.
What if a .o references a symbol that no object or library defines?
undefined reference at link time — the "looking-for" badge finds no matching "can-provide," so the linker cannot patch that relocation and refuses to produce an executable.
Does an all-zero int arr[100] = {0}; go to .data or .bss?
.bss — an explicit zero initialiser is equivalent to no initialiser for storage purposes, so the compiler stores only its size, saving 400 bytes of file space.

Recall One-line self-test

Cover the answers, walk each section top to bottom, and speak a reason aloud — not just true/false. Any item where your reason differed from the reveal is your next study target.