Question
I have the following Makefile:
Copyall: ll
ll: ll.c
gcc -c -Wall -Werror -O2 c.c ll.c -o ll $@ $<
clean:
\rm -fr ll
When I run make or make clean, I get this error:
makefile:4: *** missing separator. Stop.
What does this error mean, and how can I fix the Makefile correctly?
Short Answer
By the end of this page, you will understand what the GNU Make *** missing separator error means, why it usually happens when indentation is wrong, and how to write valid Makefile rules using targets, prerequisites, and tab-indented recipe lines.
Concept
make reads a Makefile using very strict syntax.
A basic rule looks like this:
target: prerequisites
command
The important detail is this: each command line inside a rule must begin with a tab character unless you intentionally change the recipe prefix.
The error:
*** missing separator
usually means make expected a tab-indented command line, but found spaces or some other invalid text instead.
In your file, the command lines under ll: and clean: are indented with spaces instead of a tab. GNU Make treats that as invalid syntax.
This matters because Makefiles are not like many programming languages where spaces and tabs are interchangeable. In a Makefile recipe, indentation has meaning.
There are also a couple of other issues in the example:
-02should almost certainly be-O2with a capital letterOgcc -c ... -o llis inconsistent because-ccompiles without linking and normally outputs an object file like
Mental Model
Think of a Makefile as a checklist with a very strict format:
- The target is the thing you want to build.
- The prerequisites are the files it depends on.
- The recipe is the set of shell commands used to build it.
A simple way to picture it:
ll:= the name of the jobll.c= what the job needs first- tab +
gcc ...= the actual instruction to run
If you write the instruction with spaces instead of a tab, make does not recognize it as a command. It is like filling out a form where one field must start in a specific column. If it does not, the form is rejected.
Syntax and Examples
The core Makefile syntax is:
target: prerequisite1 prerequisite2
command 1
command 2
Minimal correct example
ll: ll.c
gcc -Wall -Werror -O2 ll.c -o ll
clean:
rm -f ll
What this does
llis built fromll.c- The command compiles and links the program into an executable named
ll cleanremoves the generated executable
Better version with variables
CC = gcc
CFLAGS = -Wall -Werror -O2
ll: ll.c
$(CC) $(CFLAGS) ll.c -o ll
clean:
rm -f ll
This version is easier to maintain because compiler options are stored in variables.
If you want a default target
all: ll
ll: ll.c
gcc -Wall -Werror -O2 ll.c -o ll
clean:
rm -f ll
Now running make builds , which depends on .
Step by Step Execution
Consider this Makefile:
all: ll
ll: ll.c
gcc -Wall -Werror -O2 ll.c -o ll
clean:
rm -f ll
Now run:
make
Step by step
makelooks for the first target.- The first target is
all. alldepends onll, somakechecks whetherllexists and whether it is up to date.- To build
ll,makesees that it depends onll.c. - If
lldoes not exist, orll.cis newer thanll,makeruns:
gcc -Wall -Werror -O2 ll.c -o ll
- The executable
llis created.
Real World Use Cases
Makefiles are commonly used for:
- Compiling C and C++ programs
- Running test suites
- Cleaning generated files
- Building documentation
- Automating repetitive shell commands
Practical examples
Build an app
app: main.c utils.c
gcc -Wall main.c utils.c -o app
Run tests
test:
pytest
Clean generated files
clean:
rm -f app *.o
Build and package
release:
gcc -O2 main.c -o app
tar -czf app.tar.gz app
Even in modern projects, make is still useful as a simple task runner.
Real Codebase Usage
In real projects, developers usually write Makefiles with a few common patterns.
Variables for compiler settings
CC = gcc
CFLAGS = -Wall -Werror -O2
TARGET = ll
$(TARGET): ll.c
$(CC) $(CFLAGS) ll.c -o $(TARGET)
This avoids repeating the same flags.
.PHONY for non-file targets
Targets like clean do not usually create a file named clean. Mark them as phony:
.PHONY: all clean
all: ll
clean:
rm -f ll
This prevents conflicts if a real file named clean exists.
Separate compile and link steps
For larger projects:
CC = gcc
CFLAGS = -Wall -Werror -O2
ll: ll.o
$(CC) ll.o -o ll
ll.o: ll.c
$(CC) $(CFLAGS) -c ll.c -o ll.o
This is more scalable because only changed source files need recompilation.
Common maintenance targets
Common Mistakes
1. Using spaces instead of a tab
Broken:
ll: ll.c
gcc -Wall ll.c -o ll
Fixed:
ll: ll.c
gcc -Wall ll.c -o ll
This is the most common cause of missing separator.
2. Writing -02 instead of -O2
Broken:
gcc -02 ll.c -o ll
Fixed:
gcc -O2 ll.c -o ll
The optimization flag uses a capital letter O, not zero.
3. Mixing -c with executable output
Broken:
gcc -c ll.c -o ll
-c means compile only, producing an object file, usually ll.o.
Fixed compile-only version:
Comparisons
Recipe line vs target line
| Part | Example | Purpose |
|---|---|---|
| Target line | ll: ll.c | Declares what to build and what it depends on |
| Recipe line | \tgcc ll.c -o ll | Command that builds the target |
Tabs vs spaces in Makefiles
| Indentation | Result |
|---|---|
| Tab before recipe command | Valid |
| Spaces before recipe command | Usually causes missing separator |
Compile only vs compile and link
| Command | Meaning |
|---|
Cheat Sheet
Basic Makefile rule
target: prerequisites
command
Important rule
- Recipe commands must start with a tab, not spaces.
Common fix for missing separator
Replace this:
ll: ll.c
gcc ll.c -o ll
With this:
ll: ll.c
gcc ll.c -o ll
Simple working example
.PHONY: all clean
all: ll
ll: ll.c
gcc -Wall -Werror -O2 ll.c -o ll
clean:
rm -f ll
Useful automatic variables
$@= target name$<= first prerequisite$^= all prerequisites
Example:
ll: ll.c
gcc -o
FAQ
Why does Make say missing separator?
Usually because a recipe command starts with spaces instead of a tab.
Can I use spaces instead of tabs in a Makefile?
Not for normal recipe lines. GNU Make expects a tab unless you intentionally configure a different recipe prefix.
Why does make clean fail even though clean is simple?
Because make must parse the whole file first. A syntax error anywhere in the Makefile prevents all targets from running.
Should I use -c when building an executable?
No. -c is for compile-only mode and usually produces an object file like .o. To create an executable, compile and link without -c.
What is the difference between $@ and $<?
$@ is the target name. $< is the first prerequisite.
Why should I add .PHONY: clean?
It tells make that is a command target, not a file to build.
Mini Project
Description
Create a small Makefile for a C program that can build the executable and remove generated files. This project helps you practice correct Makefile syntax, especially target definitions and tab-indented recipe lines.
Goal
Write a working Makefile that builds a C program named hello from hello.c and supports a clean command.
Requirements
- Create a default target that builds
hello. - Compile
hello.cinto an executable namedhello. - Add a
cleantarget that removes the executable. - Use tab indentation for all recipe commands.
- Mark non-file targets appropriately.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
Definition vs Declaration in C and C++: What’s the Difference?
Learn the difference between declarations and definitions in C and C++ with simple examples, common mistakes, and practical usage.
Difference Between #include <...> and #include "..." in C and C++
Learn the difference between #include with angle brackets and quotes in C and C++, including search paths, examples, and common mistakes.