Programvaruteknik och programmering
Förstå och hantera C-programberoende med Makefiles
Table of Contents
Introduktion
I C-programmering är ett program ofta delat över flera källfiler och rubrikfiler för att förbättra organisationen, återanvändbarheten och sammanställningshastigheten. Men denna modularitet introducerar en utmaning: när en rubrikfil ändras, måste varje källfil som innehåller den rekompileras. Att göra detta manuellt är fel-benägen och tidskonsumerande. Makefiles, driven av ] gör en svart passagerarstruktur för att skapa ett automationsverktyg, lösa detta problem genom att koda beroenden och automatisera byggprocessen.
Vad är Makefiles?
] ]]]]]]] använder dessa regler, kontrollerar filtidsstämplar och utför endast de kommandon som krävs för att föra projektet uppdaterat. Kärnidén är enkel: varje regel har en ]]] (filer som behövs för att skapa ]] (filer som behövs för att bygga upp ]]
Makefiles har varit en del av Unix sedan 1970-talet, och GNU Make är de facto-standarden på Linux och macOS. Syntaxen är kortfattad men kan vara subtil; att få beroenden rätt är den primära färdigheten som en C-utvecklare behöver.
Förstå beroenden
I ett C-projekt är beroenden inte begränsade till ] filer. Varje källfil innehåller en eller flera rubrikfiler (t.ex. ]])]). Om en rubrik ändras måste alla ] filer som inkluderar den rekompileras. På samma sätt är objektfiler beroende av deras motsvarande ] filer, och den slutliga körbara beror på alla objektfiler.
Explicit vs implicita beroenden
I början av Makefiles, programmerare listade alla förutsättningar manuellt. Det tillvägagångssättet är ömtåligt: att glömma en rubrik betyder stale builds, medan listning för många utlösa onödiga rekompiler. Värre, som projektet växer, manuella listor blir oövervinneliga. Den moderna lösningen är att få kompilatorn att generera beroenden automatiskt, förvandla dem till implicita, maskinkontrollerade förutsättningar.
En välgjord Makefile behandlar beroenden som en förstklassig oro. Målet är aldrig att bygga om något som inte behöver byggas om, och att alltid bygga om allt som gör. Detta är kärnan i oriktighet ] och ]] effektivitet ] i stegvisa byggnader.
Grundläggande struktur av en makefil
En typisk Makefile innehåller varierande definitioner, regler och fonymål. Här är ett minimalt men funktionellt exempel för ett projekt med ] och :
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
Denna Makefile har fyra mål: (den körbara), två objektfiler och ett falskt mål ]]]. Beroendelinjerna efter kolon berättar ]]] gör ] vilka filer som ska kontrolleras innan de bestämmer sig för att bygga upp målet.
Phony Targets
eller ] representerar inte filer. För att förhindra ]]]]] från att förväxla dem med filnamn, bör de förklaras som ]]foni :
.PHONY: clean all
Utan detta, om en fil som heter fanns, ]]] skulle betrakta den aktuell och hoppa över receptet.
Hantera beroenden effektivt
Den manuella Makefile ovan har en allvarlig fel: beroendet av på ] är korrekt, men vad sägs om rubriker? Om ]] ändras, ]] (som inkluderar det) måste byggas om, men regeln säger att det bara beror på ].
Automatisk beroendegenerering med GCC
GCC (och Clang) kan generera beroendeinformation med hjälp av ] flaggfamiljen. Den mest praktiska kombinationen för de flesta projekt är :
- []][]]] - skriver en beroendefil (]) under sammanställningen, och listar endast användardefinierade rubriker (inte systemrubriker).
- []][]]]] – anger namnet på beroendefilen.
- []]]] lägger till falska mål för varje rubrik, vilket förhindrar fel när en rubrik tas bort.
Så här införlivar du automatisk beroendespårning i en 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
Förklaring:
- samlar alla ] filer i källkatalogen.
- ] omvandlar dem till objektfilvägar.
- listar beroendefilerna (t.ex. ).
- Mönsterregeln sammanställer varje ]-fil och på grund av ] genererar en ]]]-fil som en bieffekt.
- Ledningen ] läser de genererade ] filerna, förvandla dem till verkliga Makefile förutsättningar. Dash (]) undertrycker fel när ] filer inte finns ännu (t.ex. på den första bygg).
Om modifieras, kommer nästa fakturering av att kompensera ] automatiskt eftersom ]]-filen för ]]] inkluderar som en förutsättning.
Avancerade tekniker
Hantering av genererade beroenden säkert
När en rubrik raderas kan ]-filen fortfarande referera till den, vilket orsakar ]][] att misslyckas med ett saknat målfel. ]]]] flaggan adresserar detta genom att lägga till tomma falska regler för varje beroenderubrik. Om rubriken är borta, helt enkelt kör falska regeln (vilket inte gör någonting) och fortsätter.
Inklusive beroendefiler efter källan ändringar
En subtilitet: om en källfil lägger till eller tar bort en ], måste motsvarande ] filen regenereras. Eftersom ]] filen själv är en förutsättning för objektfilen ]]] göra kommer att märka den ändrade tidsstämpeln och rekompilen, som regenererar ] filen. Denna återgång fungerar automatiskt när den första byggnaden är klar.
Använda order-endast förutsättningar
Ibland behöver du en katalog att existera innan du bygger, men du vill inte att dess tidsstämpel ska utlösa en ombyggnad. Det är rollen som ] endast förutsättningar (separerad av ]]) ) . I mönsterregeln ovan använde vi inuti receptet; ett alternativ är:
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR):
mkdir -p $@
Detta säkerställer att katalogen skapas innan någon sammanställning, men en förändring av katalogen själv (t.ex. en ny fil inuti) kommer inte att utlösa rekompilering.
Bästa praxis för att hantera beroenden
- Använd automatisk beroendegenerering från dag ett. Även för ett enstaka filprojekt är det en god vana. Det kostar ingenting och förhindrar framtida misstag.
- ]] Hålla beroendefilerna separata från källfiler. Lägg dem i en ] eller ]]]] katalog. Detta gör rengöring lättare och undviker att klamra källträdet.
- ] Inkludera de genererade ] filerna efter de regler som skapar dem. ]]] direktivet är bra, men att placera det efter mönsterregeln säkerställer att ] först vet hur man bygger objektfiler innan man försöker läsa ] filer.
- ] Använda falska mål för hushållning. ], ]]]] ]]]] och ]]]] är vanliga. Förklara alltid dem med ]].
- ]Leverage variabler för kompilatorflaggor, källkataloger och listor över filer. Detta gör Makefile återanvändbar över projekt och lättare att anpassa.
- Testa din Makefil med en avsiktlig rubrikändring. Modifiera en rubrik, spring ]] och bekräfta endast de drabbade objektfilerna rekompileras. Om en fullständig ombyggnad sker är något fel med beroendespårningen.
- ]] Håll Makefile enkelt men inte enklare än nödvändigt. Över-teknik med avancerade funktioner som ]] kan göra felsökning smärtsamt. Börja med det mönster som visas i denna artikel; det skalar bra till dussintals filer.
Externa resurser
För att fördjupa din förståelse, rådfråga dessa auktoritativa referenser:
- ]GNU Gör manual - Den definitiva guiden för att göra syntax, funktioner och avancerade funktioner.
- ]GCC Preprocessor Options - Dokumentation för ]], ]]], ]]] och tillhörande flaggor.
- Makefile Tutorial av Chase Lambert - En praktisk, välstrukturerad handledning som täcker många verkliga användningsfall.
Slutsats
Hantera C-programberoende med Makefiles är inte en lyx utan en nödvändighet för något projekt som växer upp en enda fil. Genom att kombinera mönsterregler, automatisk beroendegenerering med ] och noggrann inkludering av de genererade ]] filer, kan du skapa ett byggsystem som är både snabbt och korrekt. De tekniker som visas här eliminerar manuell spårning, minskar byggtider och förhindrar subtila buggar som orsakas av stalobjekt.