Exercises — Symbol resolution — order matters
The picture we return to for every problem:

Level 1 — Recognition
Recall Solution L1·Q1
- Read
main.o: put it in . It definesmain→ . It usessqrt, not in → . - Read
libm.a: is any current symbol defined by a member? Yes,sqrt.odefinessqrt. Pullsqrt.oin. Nowsqrtmoves . - Answer: . Link succeeds. ✅
Recall Solution L1·Q2
- Read
libm.afirst: at this moment , so no member is pulled (nobody has asked forsqrtyet). - Read
main.o: it now needssqrt→ . Butlibm.ais already behind us; the linker never turns back. - Answer: →
undefined reference to 'sqrt'. ❌
Recall Solution L1·Q3
True. Position on the link line is part of the program's meaning for static archives, exactly because of the single-pass rule shown in Q1 vs Q2.
Level 2 — Application
Recall Solution L2·Q1
Corrected: gcc main.o -ljson. Reason: the object that asks for parse (main.o) must be scanned before the archive that provides it, so that parse is already in when libjson.a is reached.
Recall Solution L2·Q2
gcc app.o -lgfx -lmem. Trace:
app.o→ .libgfx.a→ member definingdrawpulled in; it usesalloc_buf→ (draw moved to ).libmem.a→ definesalloc_buf→ pulled in, . ✅ Rule: user before provider, so-lgfx(the user ofalloc_buf) comes before-lmem(the provider).
Recall Solution L2·Q3
main.o→ .libutil.a:a.odefineshelper→ pulla.o.a.oreferences nothing new.b.oandc.odefine symbols not in → skipped.- Answer: exactly 1 member (
a.o).b.oandc.oare dropped.
Level 3 — Analysis
Recall Solution L3·Q1
main.o: , .liba.a: does any member define something in ?ga.odefinesg, notf. Nothing pulled. still.x.o: definesf(moves to ), usesg→ .- End: → fails
undefined reference to 'g'. Why:liba.awas scanned beforex.oasked forg, soga.owas never pulled. Correct line:gcc main.o x.o liba.a.
Recall Solution L3·Q2
- Order:
-lcryptoappears before the object that referencescrypto_hash, so at the archive didn't yet contain it. - Transitive gap: the member that defines
crypto_hashitself references a further symbol not yet available, or the referencing object is itself in a later archive whose scan already completed — a circular/late dependency. (Fix via correct order or--start-group.) A third possibility for C++: name mangling mismatch, so the referenced name and the defined name differ.
Recall Solution L3·Q3
It drops libssl.so. --as-needed keeps a shared library only if, when scanned, it satisfies a current undefined. -lssl is scanned before main.o, so → libssl.so is judged "not needed" and omitted → later main.o yields an undefined SSL_new. Even for shared libraries, user-before-provider holds. Fix: gcc -Wl,--as-needed main.o -lssl.
Level 4 — Synthesis
Recall Solution L4·Q1
Naive gcc main.o -lfoo -lbar fails: pulling bar_do's member from libbar.a newly needs foo_help, but libfoo.a was already scanned → foo_help stays undefined. Fixes:
- Repeat:
gcc main.o -lfoo -lbar -lfoo(second-lfoosatisfiesfoo_help). - Group (preferred):
gcc main.o -Wl,--start-group -lfoo -lbar -Wl,--end-group, which re-scans the enclosed archives until a full pass pulls nothing new — deliberately defeating the single-pass rule.
Recall Solution L4·Q2
Two clean options:
- Link the member directly:
gcc main.o boot.o -linit(an explicit object is always kept, so its constructor is present). - Force whole archive locally:
gcc main.o -Wl,--whole-archive -linit -Wl,--no-whole-archive -lother.--whole-archiveincludes every member oflibinit.aregardless of ;--no-whole-archiveimmediately restores normal on-demand behaviour so-lotherisn't bloated. See Library Constructors and __attribute__((constructor)).
Recall Solution L4·Q3
Under legacy "common" rules: one strong + one tentative → the strong wins silently, so count == 5 and both files share that storage. Under -fno-common the tentative int count; becomes a real global definition → two strong definitions → multiple definition of 'count' error. Correct design: declare extern int count; in a header, define int count = 5; in exactly one .c.
Level 5 — Mastery
Recall Solution L5·Q1
main.o: .- Group pass 1 over
-lp -lq:libp.a:p1.odefinesf(in ) → pull.p1.ousesg→ .p2.odefinesh∉ → skip.libq.a:q1.odefinesg(in ) → pull. .q2.odefinesw∉ → skip.
- Group pass 2: nothing new pulled → group ends.
- In (besides
main.o):p1.oandq1.o.p2.oandq2.oare excluded (never in ). Answer count of extra members = 2.
Recall Solution L5·Q2
gcc main.o -lp -lq. Trace:
main.o→ .-lp:p1.odefinesf, usesg→ .-lq:q1.odefinesg→ . ✅ No cycle exists here (pdepends onq, not vice versa), so plain user-before-provider order suffices — no--start-groupneeded. This is the mastery insight: reach for order first, groups only for true cycles.
Recall Solution L5·Q3
Two compounding effects:
- Order:
-lfeaturesis scanned beforecore.oasks for its symbols, so under--as-neededthe linker sees has nolibfeaturessymbol yet and drops the.sofrom the recorded dependencies. - No link error because the plugin registers via a constructor side effect, not via a referenced symbol — nothing is "undefined," so the linker is silent; the failure only appears at runtime when the constructor never runs.
Corrected:
gcc plugin_registry.o core.o -Wl,--as-needed -lfeatures(all users before the provider). If registration still depends on an unreferenced member of a static archive, add-Wl,--whole-archive/--no-whole-archivearound it.
Recall Feynman recap (12-year-old version)
Walk down a hallway of lockers once, never turning back, carrying a to-do list of things you still need. You only open a locker if it holds something already on your list. So put the kids who need things (your objects) at the front of the hallway and the supply lockers (libraries) at the back. If two supply lockers keep needing each other, you're allowed to walk that little stretch back and forth until nothing new is grabbed — that's --start-group.
Connections
- 5.3.05 Symbol resolution — order matters (Hinglish)
- Build Systems & Toolchain
- The Linker (ld) and Linking Phases
- Static vs Shared Libraries
- Object File Format (ELF) and Symbol Tables
- Name Mangling in C++
- Library Constructors and __attribute__((constructor))