5.3.3 · D4Build Systems & Toolchain

Exercises — Static libraries — .a - .lib, creation and linking

2,435 words11 min readBack to topic

L1 — Recognition

These check that you can name the pieces. No reasoning yet, just recall the vocabulary from Object files and relocation and the parent note.

Recall Solution 1.1

Unix: libmath.a. Windows: math.lib.

  • The lib prefix and .a suffix are a convention the linker relies on: the flag -lmath literally expands to the filename libmath.a.
  • Windows drops the lib prefix; the tool lib.exe produces math.lib.
Recall Solution 1.2
  • Unix: ar (the archiver), e.g. ar rcs libmath.a add.o sub.o.
  • Windows: lib.exe (lib /OUT:math.lib add.obj). It is "ar's cousin" — same job, different name.
Recall Solution 1.3
  • T = defined in the Text (code) section → the library provides it.
  • U = Undefined → the library needs it from somewhere else. This is exactly the "provides / needs" bookkeeping the linker itself does.

L2 — Application

Now you run the pipeline. Recall the mnemonic COAL: Compile → Objects → Archive → Link.

Recall Solution 2.1
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o

Why -c? It stops the pipeline before the link stage, emitting relocatable code — machine code where addresses are still placeholders. Libraries have no main(), so there is nothing to link into an executable yet.

Recall Solution 2.2
ar rcs libmath.a add.o sub.o
  • r = insert/replace members, c = create quietly, s = write the symbol index.
  • The s builds the table add → add.o, sub → sub.o so the linker resolves symbols without scanning every member.
Recall Solution 2.3
gcc -c main.c -o main.o
gcc main.o -L. -lmath -o app
  • -L. adds the current directory to the library search path.
  • -lmath looks for libmath.a.
  • main.o comes before -lmath because the user must precede the provider (L3 explains why).

L3 — Analysis

Here you predict outcomes and explain the mechanism. The link-order rule lives here.

Figure — Static libraries — .a  -  .lib, creation and linking
Recall Solution 3.1

(A) fails. The linker walks left → right, keeping a running set of currently undefined symbols (see the figure above).

  • In (A), when it reaches -lmath the undefined set is empty (nothing has asked for add yet), so it pulls zero members from the archive. Then main.o references add → still undefined at the end → error.
  • In (B), main.o is processed first, putting add on the "wanted" list. When -lmath is reached, add is undefined, so the member defining it is pulled in and resolved. Rule: users left, libraries right — a library must appear after whatever uses it.
Recall Solution 3.2
gcc main.o -Wl,--start-group -lA -lB -Wl,--end-group -o app

Why this works: --start-group ... --end-group tells the linker to re-scan the enclosed archives repeatedly until no new undefined symbols appear — breaking the single-pass limitation for that group only.

Recall Solution 3.3

Yes, it still runs. Static linking copied add's machine code into the executable at build time. The archive was only a build-time source; the runtime executable is self-contained. (Contrast with Shared libraries — .so / .dll, where the library must be present at run time.)


L4 — Synthesis

Assemble a whole build from parts. These combine the whole pipeline plus inspection.

Recall Solution 4.1
gcc -c add.c  -o add.o
gcc -c mul.c  -o mul.o
ar  rcs libmath.a add.o mul.o
gcc -c main.c -o main.o
gcc main.o -L. -lmath -o app

Order logic: main.o needs mul; mul (inside the archive) needs add. Both are inside libmath.a, so putting main.o before -lmath works:

  1. main.o marks mul undefined.
  2. -lmath pulls mul.o, which resolves mul but marks add undefined.
  3. Within the same archive scan, ar's index lets the linker also pull add.o, resolving add. All symbols satisfied.
Recall Solution 4.2
ar t libmath.a      # (a) list members
nm   libmath.a      # (b) show symbols
  • ar tadd.o and mul.o.
  • nmadd marked T (defined) in add.o; mul marked T in mul.o; inside mul.o, add also appears marked U (mul.o needs add).
Recall Solution 4.3
cl /c add.c
cl /c mul.c
lib /OUT:math.lib add.obj mul.obj
cl main.c math.lib /Fe:app.exe

Same three COAL stages — compile (cl /c), archive (lib), link (cl ... math.lib) — just different tool names. MSVC passes the .lib directly on the command line rather than via -l.


L5 — Mastery

Edge cases and reasoning the parent note only gestured at. These separate "can run the commands" from "understands the machine."

Recall Solution 5.1

Only add.o is copied in. sub.o and mul.o are left out. Mechanism: the linker pulls a member from the archive only if that member resolves a currently-undefined symbol. Nothing references sub or mul, so those members never enter the "wanted" set. This is the selectivity goal — you get reuse without bloating the executable with unused code.

Recall Solution 5.2

Yes — giant is copied in too. The unit of extraction is the whole object file, not the individual function. Because add and giant live in the same util.o, pulling in add drags giant along. Fix (build time): put each function in its own .c → own .o so the archive has fine-grained members. (Alternatively compile with -ffunction-sections and link with -Wl,--gc-sections so the linker garbage-collects unreferenced sections.)

Recall Solution 5.3

The archive is created successfully but contains no members and no defined symbols. At link time it contributes nothing, so add stays undefined → the linker fails with undefined reference to 'add'. Lesson: an empty (or wrong) library is not an error by itself — the error surfaces only when a needed symbol goes unresolved.

Recall Solution 5.4

The first archive that resolves add wins — here libA.a, because it is scanned before libB.a in left-to-right order. Once add is satisfied, libB.a's member defining add is never pulled in (nothing undefined needs it), so there is no multiple-definition error. (This differs from linking two object files that both define add directly, which would be a duplicate-symbol error — the archive's "pull only if needed" rule quietly hides the collision.)