Move from a single .c file to a realistic header/source/Makefile layout.
.c file becomes hard to read and slow to rebuild. Splitting it up pays off in several ways:make figures it out for you).// declarations only #ifndef MATHUTIL_H #define MATHUTIL_H int add(int a, int b); int mul(int a, int b); int factorial(int n); #endif
#include "mathutil.h" int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } int factorial(int n) { if (n <= 1) return 1; return n * factorial(n - 1); }
#include <stdio.h> #include "mathutil.h" int main(void) { printf("2+3 = %d\n", add(2, 3)); printf("4*5 = %d\n", mul(4, 5)); printf("5! = %d\n", factorial(5)); return 0; }
.h lists what this module exports (declarations), while the .c contains how it actually works (implementation). A reader should be able to use the module from the header alone.#include "..." vs. <...>: use quotes for your own project's headers, and angle brackets for standard or external library headers.main.c has changed, you just recompile it and relink. That's exactly what make automates.| Flag | Meaning |
|---|---|
-c | Compile only, don't link. Produces a .o file. |
-o name | Sets the output file name. |
-Wall -Wextra | Enables the common warnings. Always use these. |
-g | Includes debug info (for use with gdb). |
-O0 / -O2 | No optimization / standard release-level optimization. |
-std=c11 / -std=c17 | Selects the C standard version. |
-I dir | Adds a header search path. |
-L dir | Adds a library search path. |
-l name | Links libname β for example, -lm for the math library. |
-D MACRO | Defines a preprocessor macro. |
.c file:// top and bottom of mathutil.h #ifndef MATHUTIL_H #define MATHUTIL_H /* header contents */ #endif
#pragma once β a single line that does the same job. It's not in the C standard, but it's widely portable.extern.c files, put an extern declaration in the header (just a name and type) and the actual definition in exactly one .c file.#ifndef CONFIG_H #define CONFIG_H extern int g_verbose; // declaration only #endif
#include "config.h" int g_verbose = 0; // the one and only definition
int g_verbose; in the header: every .c that includes it would get its own definition, producing a "multiple definition" linker error.make tool reads a Makefile, figures out which files have changed, and rebuilds only what's necessary.# Makefile β filename must start with capital M # IMPORTANT: lines under a target must start with a TAB character app: main.o mathutil.o gcc main.o mathutil.o -o app main.o: main.c mathutil.h gcc -c main.c mathutil.o: mathutil.c mathutil.h gcc -c mathutil.c clean: rm -f *.o app
CC = gcc
CFLAGS = -Wall -Wextra -O2 -g
OBJS = main.o mathutil.o
TARGET = app
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
# implicit rule handles .c β .o automatically
main.o: main.c mathutil.h
mathutil.o: mathutil.c mathutil.h
clean:
rm -f $(OBJS) $(TARGET)
.PHONY: clean
.PHONY: declares targets that aren't real files. Without it, a file named clean sitting on disk would trick make into thinking the target is already up to date.CFLAGS += -MMD -include $(OBJS:.o=.d)
-MMD flag writes out .d files listing header dependencies, so make automatically rebuilds whenever any included header changes.mathutil.h, mathutil.c, main.c, and a Makefile, then run make and ./app.mathutil.c that uses sqrt() from <math.h>. Add -lm to LDFLAGS and confirm it links successfully.make debug with -g -O0, and make release with -O2.Check your understanding of this lesson!
.c files at once?List multiple .c files on the command line and gcc will compile each one and link them all together at the end.
#ifndef / #define / #endifMany compilers also support #pragma once, but for maximum portability the classic #ifndef guard is still the standard.
target: dependencies on one line, commands on the next line prefixed with TABCommand lines must begin with a literal TAB character β this is a strict rule of make.