Introducere

În programarea C, un program este adesea împărțit prin mai multe fișiere sursă și fișiere antet pentru a îmbunătăți organizarea, reutilizarea și viteza de compilare. Cu toate acestea, această modaritate introduce o provocare: atunci când un fișier antet se schimbă, fiecare fișier sursă care include aceasta trebuie să fie recompilată. Făcând acest lucru manual este o eroare-procent și timp-consum. Makefiles, condus de ] make construiesc instrument de automatizare, rezolva această problemă prin codificare dependențe și automatizarea procesului de construcție. Mastering Makefiles este un ritual de trecere pentru orice dezvoltator serios C, dar mulți nou-veniți le trateaza ca cutii negre. Acest articol explică modul în care dependențe lucrează în Makefiles, cum să le genereze automat, și cum să se structureze un Makefile pentru fiabilitate și viteză.

Ce sunt makefiles?

Un Makefile este un fişier cu text simplu care defineşte un set de reguli pentru construirea unui proiect. make[]represtaurant aceste reguli, verifică ştampile de timp şi execută doar comenzile necesare pentru a aduce proiectul la zi. Ideea de bază este simplă: fiecare regulă are un [ ţintă (fişierul de produs), condiţii prealabile (fişiere necesare pentru construirea ţintei) şi ]recipes[ (comenzi de coajă pentru a rula). Prin înregistrarea de care fişierele depind, make] poate reconstrui progresiv numai părţile care s-au schimbat.

Makefiles au fost parte a Unix din anii 1970, iar GNU Make este standardul de facto pe Linux și MacOS. Sintaxa este concisă, dar poate fi subtilă; obținerea de dependențe dreptul este abilitatea primară de care are nevoie un C dezvoltator.

Înțelegerea dependențelor

În cadrul unui proiect C, dependențele nu se limitează la . Fiecare fișier sursă include unul sau mai multe fișiere antet (de exemplu, . Dacă un antet este modificat, toate fișierele care includ acest fișier trebuie recompilate. În mod similar, fișierele obiect depind de fișierele corespunzătoare , iar executabilul final depinde de toate fișierele de obiecte.

Dependențe explicite/implicite

La makefiles timpuriu, programatorii au listat manual toate condiţiile prealabile. Această abordare este fragilă: uitarea unui antet înseamnă construcţii vechi, în timp ce listarea prea multe declanşează recompilări inutile. Mai rău, pe măsură ce proiectul creşte, listele manuale devin de nepătruns. Soluţia modernă este de a avea compilatorul generează automat dependenţele, transformându-le în premise implicite, verificate automat.

Un Makefile bine lucrat tratează dependențele ca pe o preocupare de primă clasă. Scopul este niciodată să reconstruiască ceva care nu are nevoie de reconstruire, și să reconstruiască întotdeauna tot ceea ce face. Aceasta este esența corectitudine] și eficiență] în construcții incrementale.

Structura de bază a unui fișier make

Un makefile tipic conține definiții, reguli și ținte false. Iată un exemplu minim, dar funcțional pentru un proiect cu și :

CC = gcc
CFLAGS = -Wall -Wextra -O2

main: main.o utils.o
 $(CC) $(CFLAGS) -o main main.o utils.o

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

utils.o: utils.c
 $(CC) $(CFLAGS) -c utils.c

clean:
 rm -f main main.o utils.o

Acest Makefile are patru obiective: (executabilul), două fișiere de obiecte și o țintă falsă . Liniile de dependență după colon spun ] make care fișiere pentru a verifica înainte de a decide să reconstruiască ținta.

Obiective false

Ținte precum sau nu reprezintă fișiere. Pentru a preveni make de la a le confunda cu numele fișierelor, acestea ar trebui să fie declarate ca fals :

.PHONY: clean all

Fără aceasta, dacă un fișier numit ar exista, make ar considera că este actualizat și ar sări peste rețetă.

Gestionarea eficientă a dependenţelor

Makefile-ul manual de mai sus are un defect grav: dependenţa de [] este corectă, dar ce se întâmplă cu antetele? Dacă se schimbă, (care include)] (trebuie reconstruită, totuşi regula spune că depinde doar de . Fixarea este de a lăsa compilatorul să producă adevărata listă de dependenţă.

Generarea automată a dependenței cu CCG

GCC (și Clang) poate genera informații privind dependența folosind familia de steaguri. Combinația cea mai practică pentru majoritatea proiectelor este :

Aici se introduce o urmărire automată a dependenței într-un Makefile:

CC = gcc
CFLAGS = -Wall -Wextra -O2 -MMD -MP
SRCDIR = src
OBJDIR = obj
SRCS = $(wildcard $(SRCDIR)/*.c)
OBJS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS))
DEPS = $(OBJS:.o=.d)

all: myprogram

myprogram: $(OBJS)
 $(CC) $(CFLAGS) -o $@ $^

$(OBJDIR)/%.o: $(SRCDIR)/%.c
 @mkdir -p $(OBJDIR)
 $(CC) $(CFLAGS) -c $< -o $@

-include $(DEPS)

.PHONY: all clean
clean:
 rm -rf $(OBJDIR) myprogram

Explicație:

  • colectează toate fișierele din directorul sursă.
  • le transformă în căi de fișiere obiect.
  • enumeră fișierele de dependență (de exemplu, .
  • Regula modelului ] compila fiecare fișier și, din cauza ] generează un fișier ca efect secundar.
  • Linia citește fișierele generate , transformându-le în premise reale ale Makefile. Dana () suprimă erorile atunci când fișierele nu există încă (de exemplu, la prima construcție).

Acum, dacă este modificată, următoarea invocare a ] va recompila automat deoarece fișierul [ pentru include ca o condiție prealabilă.

Tehnici avansate

Manipularea dependențelor generate în condiții de siguranță

Atunci când un antet este șters, ] fișierul poate încă să-l facă trimitere, cauzând make să nu reușească cu o eroare țintă lipsă. Steagul se adresează acestui fapt prin adăugarea unor reguli false goale pentru fiecare antet de dependență. Dacă antetul este plecat, make pur și simplu conduce regula falsă (care nu face nimic) și continuă.

Inclusiv fișiere de dependență după modificări de sursă

O subtilitate: dacă un fișier sursă adaugă sau elimină un , fișierul [ corespunzător trebuie regenerat. Deoarece fișierul este el însuși o condiție prealabilă a fișierului obiectului, make va observa marca temporală și recompila modificată, care regenerează fișierul . Această recursie funcționează automat odată ce prima construcție este completă.

Folosirea de precondiții de ordine numai

Uneori ai nevoie de un director pentru a exista înainte de construirea, dar nu vrei ca timbrul său să declanşeze o reconstrucţie. Acesta este rolul precondiţii de ordin-doar] [separat de ]. În regula de model de mai sus, am folosit în interiorul rețetei; o alternativă este:

$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
 $(CC) $(CFLAGS) -c $< -o $@

$(OBJDIR):
 mkdir -p $@

Acest lucru asigură că directorul este creat înainte de orice compilare, dar o schimbare în directorul în sine (de exemplu, un nou fișier în interiorul) nu va declanșa recompilarea.

Cele mai bune practici pentru gestionarea dependențelor

  1. Folosiți generarea automată de dependență din prima zi.Chiar și pentru un proiect cu un singur fișier, este un obicei bun.Nu costă nimic și previne greșelile viitoare.
  2. Păstrați fișierele de dependență separate de fișierele sursă.[ Pune-le într-un ] sau director.Aceasta face curățarea mai ușoară și evită cluttering copac sursă.
  3. Include fișierele generate după regulile care le creează.[ Directiva este bună, dar plasarea ei după regula modelului asigură că ]make mai întâi știe cum să construiască fișiere obiect înainte de a încerca să citească fișierele .
  4. Folosiţi ţinte false pentru întreţinerea casei.,,], şi ] sunt comune.Spuneţi-le întotdeauna cu .
  5. Variabilele de transfer pentru steagurile compilatorului, directoarele sursă și listele de fișiere. Acest lucru face ca Makefile reutilizabil în cadrul proiectelor și mai ușor de personalizat.
  6. Testați fișierul dvs. Makefile cu o schimbare deliberată a antetului.[ Modificați un antet, executați și confirmați doar fișierele de obiect afectate sunt recompilate. Dacă se întâmplă o reconstrucție completă, ceva este greșit cu urmărirea dependenței.
  7. Păstrați fişierul make simple, dar nu mai simple decât necesare.[ Supra-inginerie cu funcții avansate cum ar fi poate face depanare dureroasă. Începeți cu modelul afișat în acest articol; aceasta cântărește bine la zeci de fișiere.

Resurse externe

Pentru a vă aprofunda înţelegerea, consultaţi aceste referinţe autoritare:

Concluzie

Gestionarea dependențelor programului C cu makefiles nu este un lux, ci o necesitate pentru orice proiect care depășește un singur fișier. Prin combinarea regulilor de model, generarea automată a dependenței cu , și includerea atentă a fișierelor generate , puteți crea un sistem de construcţie care este atât rapid cât și corect. Tehnicile afișate aici elimină urmărirea manuală, reduce timpul de construcție, și previn bug-urile subtile cauzate de fișiere vechi de obiecte. Odată ce internalizați aceste modele, nu veți mai scrie o listă de dependență manuală. Investiția în învățarea managementului adecvat al dependenței plătește de fiecare dată când executați și urmăriți doar compilarea fișierelor schimbate.