A small orientation guide, to getting a gcc and make C development environment running. Assuming everything is installed on your system.

Example source tree (physical organisation) is as follows:

├── include
│   ├── allheads.h
│   ├── engine
│   │   └── safe_sum.h
│   ├── logger.h
│   ├── one_loney_integer.h
│   └── person.h
├── src
│   ├── engine
│   │   └── safe_sum.c
│   ├── logger.c
│   ├── main.c
│   ├── person.c
│   └── person_tests.c
└── makefile

The makefile (capital M) is parsed by GNU Make, which is responsible for generating the various GCC compiler commands, required to compile this source code. For example, I could issue individual compiler commands like this to create my binaries:

gcc -Wall -g -std=gnu11 -O0 -Iinclude -Iinclude/engine `pkg-config --cflags glib-2.0` -c -o main.o src/main.c

However, this is tedious, error prone, and slow. I don’t want to have to keep a list of source files I modified, that require rebuilding. Worse, things dependant on changes also need to be rebuilt, forcing me to constantly consider the dependency graph. What a chore. GNU make to the rescue.

To build the above source tree, here is one possible makefile:


OBJECTS= main.o safe_sum.o
CFLAGS= -Wall -g -std=gnu11 -O0 -Iinclude -Iinclude/engine
CFLAGS+= `pkg-config --cflags glib-2.0`
LDLIBS+= `pkg-config --libs glib-2.0 gobject-2.0 gio-2.0` # `-l` flags e.g: -lglib-2.0

all: $(P)

$(P): $(OBJECTS)
    $(CC) $(CFLAGS) -o $(P) $(OBJECTS) $(LDLIBS)

.PHONY: clean
    rm -f $(P)
    rm -f *.o
    rm -f *.log

This tiny makefile showcases many of the useful features of make, such as variables, implicit variables, recipes, path probing with vpath, automatic make variables (e.g. $<, $@, $*), phony targets, and utility functions, which I will not delve into detail here. The offical documentation is very good.

make essentials

The GNU make Manual is fantastic.

Custom variables

OBJECTS= main.o safe_sum.o
the_day=$(shell date +%A)

Implicit variables

make is a generic system for generating files, based on other files. It is in no way, specific to a C compiler, and can be used to drive any compiler. Make is a great platform for driving common tasks, such as producing documentation, or running a test suite. To ease working with common languages and compilers, make does have a general awareness, for example, of how to (agnostically) drive a C compiler. If make detects its working with a C, standard (or implicit) variables such as CC (name of desired C compiler), CFLAGS (compiler flags), LDFLAGS (linker flags) etc kick in. See implicit variables for more.

Program for compiling C programs


Compiler flags:

CFLAGS= -Wall -g -std=gnu11 -O3 -Iinclude -Iinclude/engine

If you’re not familiar with GCC:

  • -Wall adds compiler warnings
  • -g adds symbols for debugging
  • -std=gnu11 compiler should allow code conforming to the C11 and POSIX standards
  • -O3 indicates optimization level three, which tries every trick to build faster code
  • -I include paths

While in CFLAGS territory, its important to make note of pkg-config.

pkg-config - Return metainformation about installed libraries

Hard coding include paths to third party dependencies works, it is likely to become a maintenance point in the future, worse if there are plans to share this makefile with other developers, it is unlikely to work on their system (i.e. poor portability). For example, my project requires the GNOME core utility library GLib. I could append the hardcoded path to my CFLAG like this:

CFLAGS+= -I/usr/include/glib-2.0

However, a slightly better approach is to shell out to pkg-config which will return the neatly formatted -I, using paths based on my machines installation paths. For example:

CFLAGS+= `pkg-config --cflags glib-2.0`

Linker flags for everything non-library related (e.g. -L).

  • -L (e.g. -L/usr/local/lib) is where to search for libraries to resolve symbols

Libraries that the linker (ld) needs to, link in.

LDFLAGS+= -lglib-2.0 # hard-coded version

Again, pkg-config can do linker flags, for more robust makefiles:

LDLIBS+= `pkg-config --libs glib-2.0 gobject-2.0 gio-2.0`

LDADD (ADDitional linker/ld) is useful for feeding any other additional terms to the linker, for example:

LDADD= -Llibpath -Wl,-Rlibpath
  • -L flag tells the compiler where to search for libraries to resolve symbols
  • -Wl flag passes its flags through from gcc to the linker
  • -R the linker will embed these into the runtime search path for libraries to link to

VPATH specifies a list of directories that make should search:


OK, with the compiler and linker guff out of the way, its time to direct make at our source code. make generates files from other files, using recipes, the syntax is as follows. Please note, thanks to POSIX standardisation the recipe must be indented with a tab (not spaces):

<target>: <dependency>

An example of this in action:

OBJECTS= main.o safe_sum.o

all: $(P)

$(P): $(OBJECTS)
  $(CC) $(CFLAGS) -o $(P) $(OBJECTS) $(LDLIBS)

The first target, in this case all is the default target, and is dependent on my project seething. In order to build the binary seething, make moves on looking for the seething target, which is dependent on objects main.o and safe_sum.o. The recipe defined instructs make to invoke the CC compiler, producing an output of seething. Luckily we don’t have to explain to make how to build main.o and safe_sum.o, because make is smart enough to infer a default build recipe, because we are dealing with C, will be:

$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $*.c

For safe_sum.o this will wind up being:

gcc -Wall -g -std=gnu11 -O0 -Iinclude -o safe_sum.o safe_sum.c

Automatic make variables

  • $< returns the name of the first prerequisite
  • $@ the full target filename (eg the .o compiled from .c)
  • $* target with no suffix (e.g. prog.o, $* is just prog)

Phony targets

A phony target prevents make from confusing it with a real file. For example, with .PHONY, even if a file called ‘clean’ is actually created, make clean will still execute.

.PHONY: clean
  rm -f $(P)
  rm -f *.o
  rm -f *.log

Utility functions

make provides a treasure trove of handy utility functions for common tasks such as transforming text. Below uses filter-out to strip out a couple of CFLAG options:

the_day=$(shell date +%A)

.PHONY: stringfun
  $(info $(filter-out -Wall -g -O0,$(CFLAGS)))
  $(info Ben it's $(the_day)!)

Running this:

[ben@think]$ make stringfun
-std=gnu11 -Iinclude -Iinclude/engine `pkg-config --cflags glib-2.0`
Ben it's Sunday!
make: Nothing to be done for 'stringfun'.

Other bits

make -p will dump out implicit rules and variables available