Kahibaro
Discord Login Register

6.1.1 Compiler toolchains (gcc, make)

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:

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:

  sudo apt update
  sudo apt install build-essential

build-essential pulls in gcc, g++, make, and basic headers.

  sudo dnf groupinstall "Development Tools"

or:

  sudo dnf install gcc gcc-c++ make
  sudo pacman -S base-devel

Verify:

gcc --version
make --version

Basic gcc Usage

gcc is a driver program that orchestrates preprocessing, compilation, assembling, and linking. Common single-file patterns:

  gcc main.c -o main
  ./main
  gcc -c main.c -o main.o

This is used when you have multiple source files that you’ll link together later.

  gcc main.o util.o -o myapp

Typical gcc Flags You’ll Use Often

Example:

gcc -Wall -Wextra -O2 -std=c11 main.c util.c -o myapp

Compiling Multiple Source Files with gcc

Real projects usually have multiple .c files and headers.

Project layout:

project/
  main.c
  util.c
  util.h

Step 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:

Working with Headers and Include Paths

Header files (.h) declare function prototypes, structs, constants, etc.

#include "util.h" tells the preprocessor to search:

  1. The current directory first
  2. Then directories given with -I
  3. Then system include directories

If your headers live in include/:

Project:

project/
  include/
    util.h
  src/
    main.c
    util.c

Compile 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 myapp

Linking with Libraries Using gcc

Besides your own object files, you often use libraries.

System Libraries (like libm, libpthread)

Example: linking with libm (math library):

gcc main.c -lm -o mathprog

Example: linking with a custom library:

Project:

lib/
  libfoo.a
src/
  main.c

Compile 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:

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:

Example debug build:

gcc -Wall -Wextra -O0 -g main.c util.c -o myapp_debug

Example 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:

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:

General pattern:

target: dependencies
<TAB>command
<TAB>another command

Example for a small project:

Project:

project/
  main.c
  util.c
  util.h
  Makefile

Makefile:

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.o

Usage:

make        # builds 'myapp'
./myapp
make clean  # removes build artifacts

Variables in Makefiles

Variables help you avoid repetition and centralize configuration.

Common variables:

Example:

CC = gcc
CFLAGS = -Wall -Wextra -g
LDFLAGS = -lm
myapp: main.o util.o
	$(CC) $(CFLAGS) main.o util.o $(LDFLAGS) -o myapp

Change 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:

Now adding a new source is as simple as:

SRCS = main.c util.c more.c

You 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
	./myapp

Now:

Dependency Tracking with make

For correct incremental builds, object files must be rebuilt when relevant headers change. You can:

  1. Manually list header dependencies in each rule, e.g.:
   main.o: main.c util.h config.h
   	$(CC) $(CFLAGS) -c main.c
  1. Or generate dependencies automatically (recommended for larger projects) using gcc options like -MMD -MP and including .d files 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:

Integrating gcc and make into Daily Workflow

Typical workflow in a small C project:

  1. Edit sources and headers.
  2. Run make:
    • make recompiles only changed files.
  3. Run tests or the program:
    • ./myapp or make run if you defined such a target.
  4. For debugging builds:
    • make debug (if configured) and then use a debugger.

Key advantages:

Views: 78

Comments

Please login to add a comment.

Don't have an account? Register now!