Static & Dynamic Linking, C Preprocessor, and Make – Key Vocabulary
Program–Build Pipeline and File Types
- Typical source tree components
- .c files → implementation in C
- .h files → interface / declarations
- .o files → machine-code objects produced by compiler
- Executable (no extension) → final binary produced by linker
- Libraries
- Static:
libZ.a - Shared / Dynamic:
libc.so
- Canonical workflow (editor → compiler → linker → loader → process):
- Edit
foo.c,bar.c,foo.hin an editor / IDE. - Compile each source file to an object file.
- Static objects + libraries are linked into an executable.
- Executable can then be executed, debugged, profiled, etc.
- Edit
Compiling with gcc
- Command skeleton
gcc <source-files> [options]
- Frequently used options
-c– produce only object code (skip link stage)-Wall– enable all warnings-g– embed debug symbols-o <file>– set output filename
- Example
gcc hello.c -c -Wall -g -o hello.o
Linking with gcc or ld
- Command skeleton
gcc <object-files> [options]
- Popular options
-l<lib>– link against lib.a or lib.so -g– carry debug information forward-o <file>– specify executable name
- Example
gcc hello.o goodbye.o -g -lmyexample -o hello
Static Libraries
- Definition: archive of object modules inserted into the program at link time.
- Library exports symbols.
- Program objects contain unresolved symbols.
- Linker copies only the needed modules, resolves symbols → executable complete.
- Name convention:
lib<name>.a.
Building a Static Library with ar
- Syntax
ar rcs lib<libname>.a <object-files>
- Flags
r– replace existing membersc– create the archive if missings– write symbol index (speeds relocation)
- Example
ar rcs libmyexample.a a.o b.o c.o d.o
- You link against
-lmyexamplenot directly againstlibmyexample.a.
Dynamic (Shared) Libraries
- Collection of position-independent code resolved at load time or later.
- Loader, not linker, maps required modules into the process address space.
- Symbols can resolve:
- When the process starts.
- Lazily, on first call (runtime binding).
- Name convention:
lib<name>.so.
Building a Shared Library
- Compile every source to PIC (Position-Independent Code)
gcc -fpic -c -Wall -g a.c -o a.o
- Link objects into shared object
gcc -shared -o libmyexample.so a.o b.o c.o d.o
- PIC uses relative addressing, so code can live at any virtual address.
Static vs Dynamic Linking — Trade-offs
- Static (+):
- All uncertainties resolved once at link time.
- No version-mismatch surprises.
- Link-time optimizer may inline or remove dead library code.
- Dynamic (+):
- Smaller executables; common code lives in one place on disk & in memory.
- System updates patch libraries once, fixing every dependent program.
The C Preprocessor (CPP)
- Pre-compile phase that scans for
#directives and transforms source accordingly. - Output is then compiled by the normal compiler.
#include — file inclusion
#include "foo.h"→ compiler searches current directory first.#include <foo.h>→ search system paths & paths added via-I<path>.- Systems code typically prefers the
<...>form for clarity about search path.
#define / #undef — symbolic substitution
- Create a textual macro the preprocessor will search-replace.
- Classic use: constants that might change
#define NUMBER_ENTRIES 15
...
float myFloats[NUMBER_ENTRIES];
- Undefining:
#undef NUMBER_ENTRIES.
Macros as In-lined Functions
- Syntax:
#define name(params) replacement - Expansion occurs before compilation → zero function-call overhead.
- Example: swap integers
#define swap(x,y) {int temp=x; x=y; y=temp;}
...
swap(i,j);
(Use parentheses & do{...}while(0) idiom in production to avoid pitfalls.)
Conditional Compilation
- Directives:
#if,#ifdef,#ifndef,#else,#elif,#endif. - Enable/disable code regions based on compile-time expressions.
- Widely used for portability, feature flags, temporary debugging.
- Quick comment-out pattern:
#if 0
/* dead code here */
#endif
The make Utility
- Automates multi-file builds by figuring out:
- Out-of-date pieces via file timestamps.
- Dependencies between pieces.
- Commands necessary to regenerate targets.
- Mastering make is essential for systems programmers.
Core Terminology
- Makefile – file that defines the build. Usually named
Makefileormakefile. - Target – file to build (object, executable, DOC, etc.).
- Prerequisites / Dependencies – files required to build target.
- Rule (aka production): mapping of prereqs → commands → target.
- Variables – placeholders for repeated fragments (compiler flags, lists, etc.).
Rule Syntax
target: prereq1 prereq2 ... # (colon then list)
<TAB> command line 1
<TAB> command line 2
- TAB character is mandatory before each command.
- Key idea: run commands iff target is older than any prerequisite.
Object-File Example
sample: sample.o support.o
gcc sample.o support.o -o sample
sample.o: sample.c support.h
gcc -c -Wall -I. sample.c -o sample.o
support.o: support.c support.h
gcc -c -Wall -I. support.c -o support.o
- Implicit dependency graph:
sample←sample.o,support.osample.o←sample.c,support.hsupport.o←support.c,support.h
Running make
- Simply type
makein shell. - Make reads default Makefile, walks dependency graph, rebuilds outdated targets.
- Editing
support.conly triggers rebuild ofsupport.othen re-linkingsample.
Makefile Variables
- Syntax
CC = gcc
CFLAGS = -c -Wall -I.
OBJS = a.o b.o c.o
- Usage: refer with
$(CC),$(OBJS), etc.
Built-in Automatic Variables
$@– target name$^– list of prereqs$<– first prereq- Helpful for concise rules.
Suffix Rules (OBSOLETE style)
- Declare suffixes
.SUFFIXES: .c .o
- Provide default transformation
.c.o:
$(CC) $(CFLAGS) $< -o $@
- Explicit commands become unnecessary for each
.obuild.
Pattern Rules (modern)
- Wildcard
%stands for any non-empty substring. - General form
%.o: %.c %.h
$(CC) $(CFLAGS) $< -o $@
- Works for foo.o ← foo.c foo.h, bar.o ← bar.c bar.h, etc.
Putting It All Together — Sample Makefile (JBOD Project)
CC = gcc
CFLAGS = -C -Wall -I. -fpic -g -fbounds-check
LIBS = -lcrypto
OBJS = tester.o util.o mdadm.o
%.o: %.c %.h
$(CC) $(CFLAGS) $< -o $@
tester: $(OBJS) jbod.o
$(CC) -o $@ $^ $(LIBS)
clean:
rm -f $(OBJS) tester
- Highlights
- Uses pattern rule for every
.o. $@(target) &$^(all prereqs) elegantly build final binary.cleanpseudo-target removes intermediate files.
- Uses pattern rule for every
Numerical / Symbolic References Recap
- Constant defined in example: NUMBER_ENTRIES = 15.
- Flags summary (library): r,\; c,\; s.
- Key compile option: -fpic ensures position-independent object code.
Practical & Ethical Implications
- Versioning Safety – Static linking guarantees reproducible builds; dynamic linking demands vigilance with API compatibility.
- Security Updates – Dynamic libraries let OS vendors patch vulnerabilities broadly (e.g., OpenSSL’s
libcrypto.so). - License Compliance – Statically embedding GPL code may impose stricter obligations than dynamic linking.
Study Reminders
- Memorize
gcccompile vs link flags. - Practice writing a minimal static and dynamic library.
- Create a Makefile from scratch using pattern rules and automatic variables.
- Re-impl swap macro with proper parentheses to avoid side effects.
- Experiment with
#if 0to quickly disable blocks while debugging.