Table of Contents
Overview: What a Compiler Toolchain Is on Linux
On Linux, a compiler toolchain is the set of tools that turns source code into executable programs. For C/C++ development, the most common GNU tools you’ll meet are:
gcc— the GNU C Compiler (and front-end for other languages like C++)g++— the GNU C++ Compiler (same driver asgcc, different defaults)as— the GNU assembler (usually invoked bygccfor you)ld— the linker (also driven bygcc)make— the build automation tool that coordinates compilation steps
This chapter focuses on using gcc and make as a developer on Linux: how to compile, link, and manage non-trivial builds.
Installing gcc and make
On most distributions, you install a “development tools” group or the specific packages:
- Debian/Ubuntu:
sudo apt update
sudo apt install build-essential
build-essential pulls in gcc, g++, make, and basic headers.
- Fedora/RHEL:
sudo dnf groupinstall "Development Tools"or:
sudo dnf install gcc gcc-c++ make- Arch Linux:
sudo pacman -S base-develVerify:
gcc --version
make --versionBasic gcc Usage
gcc is a driver program that orchestrates preprocessing, compilation, assembling, and linking. Common single-file patterns:
- Compile and link into an executable:
gcc main.c -o main
./main- Compile only, producing an object file (
.o):
gcc -c main.c -o main.oThis is used when you have multiple source files that you’ll link together later.
- Link multiple object files:
gcc main.o util.o -o myappTypical gcc Flags You’ll Use Often
-o output— set output file name.-c— compile to object file, do not link.-Wall— enable common warnings.-Wextra— extra warnings beyond-Wall.-Werror— treat warnings as errors.-g— include debug symbols for use withgdb.-O0,-O1,-O2,-O3,-Os— optimization levels.-std=c11,-std=c99, etc. — choose C standard.-I/path— add an include search path.-L/path— add a library search path.-lm,-lpthread, etc. — link with libraries.
Example:
gcc -Wall -Wextra -O2 -std=c11 main.c util.c -o myappCompiling Multiple Source Files with gcc
Real projects usually have multiple .c files and headers.
Project layout:
project/
main.c
util.c
util.hStep 1: Compile Each Source to an Object File
gcc -Wall -Wextra -O2 -c main.c -o main.o
gcc -Wall -Wextra -O2 -c util.c -o util.o
Each .c is compiled independently. Changes in one file usually mean only that file needs recompilation.
Step 2: Link the Object Files into an Executable
gcc main.o util.o -o myapp
gcc automatically:
- Invokes the linker (
ld) - Adds standard C runtime startup code
- Links against the standard C library (
libc) by default
Working with Headers and Include Paths
Header files (.h) declare function prototypes, structs, constants, etc.
#include "util.h" tells the preprocessor to search:
- The current directory first
- Then directories given with
-I - Then system include directories
If your headers live in include/:
Project:
project/
include/
util.h
src/
main.c
util.cCompile with an extra include path:
gcc -Iinclude -c src/main.c -o main.o
gcc -Iinclude -c src/util.c -o util.o
gcc main.o util.o -o myappLinking with Libraries Using gcc
Besides your own object files, you often use libraries.
System Libraries (like libm, libpthread)
-l<name>useslib<name>.soorlib<name>.a-L<dir>adds directories to the library search path
Example: linking with libm (math library):
gcc main.c -lm -o mathprogExample: linking with a custom library:
Project:
lib/
libfoo.a
src/
main.cCompile and link:
gcc -c src/main.c -o main.o
gcc main.o -Llib -lfoo -o myapp
Note: order matters on some platforms — generally put -lfoo after the objects that use it.
Object Files, Static Libraries, and Shared Libraries (Briefly)
gcc and associated tools support different artifact types:
- Object file:
.o— compiled machine code for one translation unit. - Static library:
.a— an archive of.ofiles. - Shared library:
.so— dynamically loaded at runtime.
Creating a static library from object files:
gcc -c util1.c util2.c
ar rcs libutil.a util1.o util2.o
gcc main.c -L. -lutil -o app
Shared libraries and runtime linking details go deeper than this chapter; here you just need to recognize the basic forms and that gcc handles linking them.
Using gcc for Debug vs Release Builds
A typical pattern:
- Debug build:
- No optimization (
-O0or low optimization) - Debug info (
-g) - Many warnings enabled
- Release build:
- Higher optimization (
-O2or-O3) - Possibly
-DNDEBUGto disable asserts - Still keep warnings enabled
Example debug build:
gcc -Wall -Wextra -O0 -g main.c util.c -o myapp_debugExample release build:
gcc -Wall -Wextra -O2 -DNDEBUG main.c util.c -o myapp
Build systems (make, CMake, etc.) are typically used to switch between profiles more conveniently.
Introduction to make
make automates builds using a file called Makefile. It:
- Knows which files depend on which
- Rebuilds only what changed
- Calls the commands you define (typically
gcc)
Instead of manually retyping compile commands for every file, you write rules once and let make figure out when and how to run them.
make uses file timestamps to decide what needs rebuilding: if a source file is newer than its corresponding object file, that object file will be rebuilt.
Basic Makefile Structure
A Makefile is composed of:
- Targets — names of files or actions to build
- Dependencies — files the target depends on
- Recipes — commands to run (must start with a tab character)
General pattern:
target: dependencies
<TAB>command
<TAB>another commandExample for a small project:
Project:
project/
main.c
util.c
util.h
MakefileMakefile:
CC = gcc
CFLAGS = -Wall -Wextra -O2
myapp: main.o util.o
$(CC) $(CFLAGS) main.o util.o -o myapp
main.o: main.c util.h
$(CC) $(CFLAGS) -c main.c
util.o: util.c util.h
$(CC) $(CFLAGS) -c util.c
clean:
rm -f myapp main.o util.oUsage:
make # builds 'myapp'
./myapp
make clean # removes build artifactsVariables in Makefiles
Variables help you avoid repetition and centralize configuration.
- Define:
CC = gcc - Use:
$(CC)
Common variables:
CC— C compilerCXX— C++ compilerCFLAGS— flags passed toCCCXXFLAGS— flags forCXXLDFLAGS— linker flags
Example:
CC = gcc
CFLAGS = -Wall -Wextra -g
LDFLAGS = -lm
myapp: main.o util.o
$(CC) $(CFLAGS) main.o util.o $(LDFLAGS) -o myappChange build behavior in one place by editing variable definitions.
Pattern Rules and Automatic Variables
Pattern rules avoid writing nearly identical rules for every .c file.
Pattern Rule for Compiling Any .c to .o
CC = gcc
CFLAGS = -Wall -Wextra -O2
SRCS = main.c util.c
OBJS = $(SRCS:.c=.o)
myapp: $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o myapp
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@Key pieces:
SRCS— list of source files.OBJS— convert all.cto.ousing substitution:$(SRCS:.c=.o).%.o: %.c— generic rule: “to build any.ofrom the corresponding.c, run this recipe.”$<— “first prerequisite” (here, the source file).$@— “the target” (here, the object file).
Now adding a new source is as simple as:
SRCS = main.c util.c more.cYou can then run:
make
and make figures out the rest.
Phony Targets
Some targets do not correspond to files (e.g., clean, test). Mark them as .PHONY to prevent filename clashes and ensure they always run.
Example:
.PHONY: clean run
clean:
rm -f myapp *.o
run: myapp
./myappNow:
make cleanalways runs thermcommand.make runbuildsmyappif needed, then runs it.
Dependency Tracking with make
For correct incremental builds, object files must be rebuilt when relevant headers change. You can:
- Manually list header dependencies in each rule, e.g.:
main.o: main.c util.h config.h
$(CC) $(CFLAGS) -c main.c- Or generate dependencies automatically (recommended for larger projects) using
gccoptions like-MMD -MPand including.dfiles in your Makefile.
A simple example of automatic dependency generation:
CC = gcc
CFLAGS = -Wall -Wextra -O2 -MMD -MP
SRCS = main.c util.c
OBJS = $(SRCS:.c=.o)
DEPS = $(SRCS:.c=.d)
myapp: $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o myapp
-include $(DEPS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f myapp $(OBJS) $(DEPS)
This way, if util.h changes, the correct .o files are automatically rebuilt.
Using make for Different Build Configurations
You can define targets or variables for debug vs release builds.
Example:
CC = gcc
CFLAGS_DEBUG = -Wall -Wextra -O0 -g
CFLAGS_RELEASE = -Wall -Wextra -O2 -DNDEBUG
SRCS = main.c util.c
OBJS = $(SRCS:.c=.o)
.PHONY: all debug release clean
all: release
debug: CFLAGS = $(CFLAGS_DEBUG)
debug: myapp
release: CFLAGS = $(CFLAGS_RELEASE)
release: myapp
myapp: $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o myapp
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f myapp $(OBJS)Usage:
make debugmake release(or justmake)
Integrating gcc and make into Daily Workflow
Typical workflow in a small C project:
- Edit sources and headers.
- Run
make: makerecompiles only changed files.- Run tests or the program:
./myappormake runif you defined such a target.- For debugging builds:
make debug(if configured) and then use a debugger.
Key advantages:
gccprovides fine control over compilation and linking.makekeeps builds incremental and reproducible.- Simple Makefiles scale reasonably well up to medium-size projects; larger projects might layer additional build systems on top, but still rely on
gccandmakeconcepts under the hood.