Variables and Automatic Variables

Lesson 3: Variables and Automatic Variables

Real Makefiles would be unreadable if you typed every compiler command in full. Variables let you define values once and reuse them โ€” and Make's automatic variables hand you the current rule's target and prerequisites for free.

Defining and Using Variables

CC      = gcc
CFLAGS  = -Wall -O2
TARGET  = myapp

$(TARGET): main.o util.o
	$(CC) $(CFLAGS) -o $(TARGET) main.o util.o

Reference a variable with $(NAME) (or ${NAME}). Make expands it wherever it appears โ€” in targets, prerequisites, and recipes.

The Four Assignment Operators

This is where newcomers get confused. The operator you choose changes when the value is expanded:

OperatorNameBehavior
=RecursiveExpanded every time it's used; can reference variables defined later. Flexible but can surprise you.
:=SimpleExpanded immediately at definition time. Predictable โ€” recommended for most cases.
?=ConditionalSet only if not already defined. Perfect for user-overridable defaults.
+=AppendAdds text to an existing variable.
# Simple (:=) โ€” evaluated NOW
PREFIX := /usr/local

# Conditional (?=) โ€” only set if the user didn't pass one
PREFIX ?= /opt/app

# Append (+=) โ€” adds to whatever's there
CFLAGS += -g
Key idea: use := by default for predictability. Use ?= for anything you want users to override on the command line (make PREFIX=/tmp/foo). Reach for = only when you genuinely need lazy evaluation.

Command-Line Overrides

Any variable can be overridden at invocation time, without editing the file:

$ make CFLAGS="-O0 -g" TARGET=debug-app

Command-line values beat file assignments (unless the file uses the override directive โ€” rare, and usually a smell). This is how packaging systems and CI pipelines customize one Makefile for many environments.

Automatic Variables: $@, {{LESSON_CONTENT}}lt;, $^, and Friends

Inside a recipe, Make provides automatic variables that describe the rule currently being executed. They're the secret to writing rules that work for any file:

VariableMeaning
$@The target of the current rule.
{{LESSON_CONTENT}}lt;The first prerequisite.
$^All prerequisites, deduplicated.
$+All prerequisites, duplicates kept.
$?Prerequisites that are newer than the target โ€” the ones that triggered the rebuild.
$*The "stem" matched by a pattern rule (Lesson 5).
# A rule that compiles any single .c file into a .o file
%.o: %.c
	$(CC) $(CFLAGS) -c {{LESSON_CONTENT}}lt; -o $@

# A link rule: $^ expands to all objects
myapp: main.o util.o
	$(CC) $(CFLAGS) -o $@ $^

When Make runs the pattern rule for util.o: $@ becomes util.o, {{LESSON_CONTENT}}lt; becomes util.c. One rule, any number of files.

% pattern rule โ€” one template, many files %.o : %.c $@ = main.o {{LESSON_CONTENT}}lt; = main.c $@ = util.o {{LESSON_CONTENT}}lt; = util.c

Environment Variables and Make

Make automatically imports environment variables as Make variables. If your shell has CFLAGS exported, Make sees it โ€” though file assignments take precedence. This is handy but can cause "works on my machine" surprises; a common hardening step is:

unexport CFLAGS   # ignore CFLAGS from the environment

Naming Conventions That Matter

Make has strong conventions for certain variables, and following them unlocks free features:

  • CC โ€” the C compiler (defaults to cc).
  • CXX โ€” the C++ compiler.
  • CFLAGS / CXXFLAGS โ€” compiler flags.
  • LDFLAGS / LDLIBS โ€” linker flags and libraries.
  • PREFIX, DESTDIR โ€” install locations.
Pro tip: make -p prints the entire default database โ€” every built-in variable and implicit rule. It's the best way to discover what Make knows out of the box.

๐Ÿง  Knowledge Check

1. What does the := assignment operator do?

2. In the rule %.o: %.c being executed for main.o, what does $@ expand to?

3. How do you override a Makefile variable without editing the file?

Further Reading