make și Makefile în C

Compilare și build POSIX make

make citește un Makefile și recompilează doar fișierele modificate, ca să nu scrii de fiecare dată comenzile de compilare.

Sintaxă
tinta: dependente
	comanda          # rândul începe cu TAB

CC = clang
CFLAGS = -std=c23 -Wall -Wextra
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

make            # construiește prima țintă
make clean      # rulează ținta clean

Ce face make și Makefile

Într-un proiect cu multe fișiere .c, a recompila totul după fiecare modificare durează. make citește un fișier numit Makefile cu reguli de forma țintă: dependențe urmate de comenzi. O țintă este refăcută doar dacă lipsește sau dacă una dintre dependențe este mai nouă decât ea (după data modificării fișierelor).

Fiecare comandă dintr-o regulă începe cu un caracter TAB, nu cu spații. Variabilele (CC, CFLAGS) se folosesc cu $(NUME). Variabile automate: $@ (ținta), $< (prima dependență), $^ (toate dependențele). O regulă cu tipar (%.o: %.c) descrie cum se face orice fișier .o din .c-ul cu același nume.

make fără argumente construiește prima țintă din fișier. make clean rulează ținta clean, care șterge de obicei fișierele generate. .PHONY: clean spune că clean nu este un fișier. make -n afișează comenzile fără să le execute.

Parametri

Parametru Descriere
$@ Numele țintei regulii.
$&lt; Prima dependență (de exemplu fișierul .c).
$^ Toate dependențele, fără dubluri.
make -n Afișează comenzile care s-ar executa, fără să le ruleze.
make -j4 Rulează până la 4 comenzi în paralel.

Exemple

Exemplul 1: Un Makefile pentru un catalog cu două fișiere

La prima rulare, make compilează ambele fișiere și le leagă. A doua oară nu are nimic de făcut. Dacă modifici doar note.c, se recompilează doar el și se refac legăturile.

# Makefile (rândurile cu comenzi încep cu TAB)
CC = clang
CFLAGS = -std=c23 -Wall -Wextra -O2

catalog: main.o note.o
	$(CC) $(CFLAGS) main.o note.o -o catalog

main.o: main.c note.h
	$(CC) $(CFLAGS) -c main.c

note.o: note.c note.h
	$(CC) $(CFLAGS) -c note.c

clean:
	rm -f *.o catalog

.PHONY: clean

# în terminal:
# make
# make
# (după o modificare în note.c)
# make

Ieșire în terminal

clang -std=c23 -Wall -Wextra -O2 -c main.c
clang -std=c23 -Wall -Wextra -O2 -c note.c
clang -std=c23 -Wall -Wextra -O2 main.o note.o -o catalog
make: `catalog' is up to date.
clang -std=c23 -Wall -Wextra -O2 -c note.c
clang -std=c23 -Wall -Wextra -O2 main.o note.o -o catalog

Exemplul 2: Regulă cu tipar și variabile automate

%.o: %.c note.h se aplică tuturor fișierelor obiect. $< devine main.c sau note.c, $@ fișierul .o, iar $^ toată lista de obiecte. make -n arată comenzile fără să le ruleze.

# Makefile
CC = clang
CFLAGS = -std=c23 -Wall -Wextra -O2
OBIECTE = main.o note.o

catalog: $(OBIECTE)
	$(CC) $(CFLAGS) $^ -o $@

%.o: %.c note.h
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBIECTE) catalog

.PHONY: clean

# în terminal:
# make -n

Ieșire în terminal

clang -std=c23 -Wall -Wextra -O2 -c main.c -o main.o
clang -std=c23 -Wall -Wextra -O2 -c note.c -o note.o
clang -std=c23 -Wall -Wextra -O2 main.o note.o -o catalog

Exemplul 3: Curățarea fișierelor generate

make clean afișează și execută comanda din ținta clean. Pentru că clean este declarată .PHONY, regula rulează chiar dacă în director ar exista un fișier numit clean.

# în terminal, în directorul cu Makefile-ul din primul exemplu:
make clean
ls -1

Ieșire în terminal

rm -f *.o catalog
Makefile
main.c
note.c
note.h

Greșeli frecvente

  • Indentezi comenzile cu spații. make cere TAB și răspunde cu „Makefile:5: *** missing separator. Stop.”.
  • Uiți antetul în lista de dependențe (main.o: main.c fără note.h). Când modifici antetul, make nu recompilează nimic și programul folosește declarații vechi.
  • Numești o țintă ca un fișier existent (clean, test) fără .PHONY. Dacă fișierul există, make crede că ținta este deja făcută.

Comenzi înrudite

  • Compilarea cu gcc și clang: Cum transformi codul C într-un program executabil cu gcc sau clang și ce fac opțiunile folosite cel mai des.
  • Programe din mai multe fișiere (.c și .h): Cum împarți un program C în fișiere sursă (.c) și antete (.h), cum le compilezi separat și cum le legi într-un executabil.
  • #include: Copiază conținutul unui fișier antet (.h) în locul liniei, ca funcțiile și tipurile declarate acolo să poată fi folosite.
  • extern: Declară o variabilă globală definită în alt fișier (sau mai jos), ca să o poți folosi fără să creezi o copie nouă.