Building and linking: static and shared libraries
Read a symbol table, build archives and shared objects, get link order right, and debug the undefined reference errors that follow.
Object files and symbols
Compiling (-c) and linking are separate steps. A link error means the compiler was satisfied but the linker could not resolve a name; reading the symbol table tells you which side is missing.
cc -std=c17 -Wall -Wextra -c mathx.c -o mathx.o
nm mathx.o # T = defined text, U = undefined, D = data
nm -C mathx.o # demangle C++ names
objdump -d mathx.o # disassembly
readelf -h mathx.o # ELF header, architecture and type
# why a symbol is missing
nm -u main.o # everything main.o still needs| Symbol letter | Meaning | Linker view |
|---|---|---|
T / t | Text (code), global / local | Provides the symbol |
D / B | Initialised / uninitialised data | Provides the symbol |
U | Undefined | Must be satisfied by another object or library |
R | Read-only data | String literals and const tables |
W | Weak symbol | Overridable by a strong definition |
Archives and shared objects
# static library: an archive of object files, copied into the final binary
cc -c src/a.c src/b.c
ar rcs libmylib.a a.o b.o
ranlib libmylib.a # build the index (rcs already does this)
cc main.c -L. -lmylib -o app_static
# shared library: position-independent code, loaded at run time
cc -fPIC -c src/a.c src/b.c
cc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0 a.o b.o
ln -sf libmylib.so.1.0.0 libmylib.so.1
ln -sf libmylib.so.1 libmylib.so
cc main.c -L. -lmylib -Wl,-rpath,'$ORIGIN' -o app_shared
ldd app_shared # which shared libraries it will load
nm -D --defined-only libmylib.so.1.0.0-fPICis mandatory for shared objects; forgetting it gives a relocation error at link time.-lfoomeanslibfoo.sothenlibfoo.a, searched in each-Ldirectory. Put libraries after the objects that need them.ldconfigand/etc/ld.so.conf.dcontrol the system search path;-Wl,-rpathbakes a path into the binary instead.--whole-archiveforces every object out of a static archive, useful for registration-based designs.
Link order is the most common cause of an undefined reference. The linker resolves left to right and does not revisit a library it has already passed, so a library that depends on another must come first.
pkg-config and diagnosing link failures
pkg-config --cflags --libs libcurl # -> -I... -lcurl
cc main.c $(pkg-config --cflags --libs libcurl) -o app
# typical failures and their real meaning
# undefined reference to 'sqrt' -> add -lm
# cannot find -lmylib -> -L path wrong or library not built
# relocation R_X86_64_PC32 ... recompile with -fPIC
# duplicate symbol -> two strong definitions, often a header definition
# version GLIBC_2.34 not found -> binary built on a newer distribution💡
A static library only pulls in the object files that are actually referenced. If your library registers callbacks through a file nothing calls directly, the linker discards it — link that object explicitly or use
--whole-archive.FAQ
Static or shared?
Static for self-contained tools and predictable deployment; shared to save disk and memory across many processes and to ship security fixes without relinking. Shared also means you must manage symbol visibility and ABI compatibility.
What does -fvisibility=hidden do?
It makes every symbol private unless explicitly exported with
__attribute__((visibility("default"))). This shrinks the dynamic symbol table, speeds loading and prevents accidental collisions between libraries.Related
Preprocessor, headers and multi-file projects Debugging with gdb, sanitizers and valgrind
Last refreshed 2026-09-18.