5.3.5 · D4Build Systems & Toolchain

Exercises — Symbol resolution — order matters

2,313 words11 min readBack to topic

The picture we return to for every problem:

Figure — Symbol resolution — order matters

Level 1 — Recognition

Recall Solution L1·Q1
  • Read main.o: put it in . It defines main. It uses sqrt, not in .
  • Read libm.a: is any current symbol defined by a member? Yes, sqrt.o defines sqrt. Pull sqrt.o in. Now sqrt moves .
  • Answer: . Link succeeds. ✅
Recall Solution L1·Q2
  • Read libm.a first: at this moment , so no member is pulled (nobody has asked for sqrt yet).
  • Read main.o: it now needs sqrt. But libm.a is 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 defining draw pulled in; it uses alloc_buf (draw moved to ).
  • libmem.a → defines alloc_buf → pulled in, . ✅ Rule: user before provider, so -lgfx (the user of alloc_buf) comes before -lmem (the provider).
Recall Solution L2·Q3
  • main.o.
  • libutil.a: a.o defines helper → pull a.o. a.o references nothing new. b.o and c.o define symbols not in → skipped.
  • Answer: exactly 1 member (a.o). b.o and c.o are dropped.

Level 3 — Analysis

Recall Solution L3·Q1
  • main.o: , .
  • liba.a: does any member define something in ? ga.o defines g, not f. Nothing pulled. still.
  • x.o: defines f (moves to ), uses g.
  • End: fails undefined reference to 'g'. Why: liba.a was scanned before x.o asked for g, so ga.o was never pulled. Correct line: gcc main.o x.o liba.a.
Recall Solution L3·Q2
  1. Order: -lcrypto appears before the object that references crypto_hash, so at the archive didn't yet contain it.
  2. Transitive gap: the member that defines crypto_hash itself 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 -lfoo satisfies foo_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-archive includes every member of libinit.a regardless of ; --no-whole-archive immediately restores normal on-demand behaviour so -lother isn'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 definitionsmultiple 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.o defines f (in ) → pull. p1.o uses g. p2.o defines h → skip.
    • libq.a: q1.o defines g (in ) → pull. . q2.o defines w → skip.
  • Group pass 2: nothing new pulled → group ends.
  • In (besides main.o): p1.o and q1.o. p2.o and q2.o are excluded (never in ). Answer count of extra members = 2.
Recall Solution L5·Q2

gcc main.o -lp -lq. Trace:

  • main.o.
  • -lp: p1.o defines f, uses g.
  • -lq: q1.o defines g. ✅ No cycle exists here (p depends on q, not vice versa), so plain user-before-provider order suffices — no --start-group needed. This is the mastery insight: reach for order first, groups only for true cycles.
Recall Solution L5·Q3

Two compounding effects:

  1. Order: -lfeatures is scanned before core.o asks for its symbols, so under --as-needed the linker sees has no libfeatures symbol yet and drops the .so from the recorded dependencies.
  2. 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-archive around 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))