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.h
in 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.
Compiling with gcc
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
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
ar rcs lib<libname>.a <object-files>
- Flags
r
– replace existing members c
– create 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
- 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
Makefile
or makefile
. - 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.o
sample.o
← sample.c
, support.h
support.o
← support.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
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
.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.