AR

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):
    1. Edit foo.c, bar.c, foo.h in an editor / IDE.
    2. Compile each source file to an object file.
    3. Static objects + libraries are linked into an executable.
    4. Executable can then be executed, debugged, profiled, etc.

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
    • rreplace existing members
    • ccreate the archive if missing
    • s – write symbol index (speeds relocation)
  • Example
  ar rcs libmyexample.a a.o b.o c.o d.o
  • You link against -lmyexample not directly against libmyexample.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

  1. Compile every source to PIC (Position-Independent Code)
   gcc -fpic -c -Wall -g a.c -o a.o
  1. 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:
    1. Out-of-date pieces via file timestamps.
    2. Dependencies between pieces.
    3. Commands necessary to regenerate targets.
  • Mastering make is essential for systems programmers.

Core Terminology

  • Makefile – file that defines the build. Usually named Makefile or makefile.
  • Target – file to build (object, executable, DOC, etc.).
  • Prerequisites / Dependencies – files required to build target.
  • Rule (aka production): mapping of prereqscommandstarget.
  • 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:
    • samplesample.o, support.o
    • sample.osample.c, support.h
    • support.osupport.c, support.h

Running make

  • Simply type make in shell.
  • Make reads default Makefile, walks dependency graph, rebuilds outdated targets.
  • Editing support.c only triggers rebuild of support.o then re-linking sample.

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)

  1. Declare suffixes
   .SUFFIXES: .c .o
  1. Provide default transformation
   .c.o:
    $(CC) $(CFLAGS) $< -o $@
  • Explicit commands become unnecessary for each .o build.

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.
    • clean pseudo-target removes intermediate files.

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 gcc compile 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 0 to quickly disable blocks while debugging.