Software Engineering en Programmering
C-programma-afhankelijkheden begrijpen en beheren met Makefiles
Table of Contents
Inleiding
In C-programmering wordt een programma vaak verdeeld over meerdere bronbestanden en headerbestanden om de organisatie, herbruikbaarheid en compilatiesnelheid te verbeteren. Echter, deze modulariteit introduceert een uitdaging: wanneer een headerbestand verandert, moet elk bronbestand dat het bevat worden gerecompileerd. Dit handmatig doen is foutgevoelig en tijdrovend. Makefiles, aangedreven door de make[] bouwen automatiseringshulpmiddel, dit probleem oplossen door het coderen van afhankelijkheden en automatiseren van het bouwproces. Mastering Makefiles is een overgangsrite voor elke serieuze C-ontwikkelaar, maar toch veel nieuwkomers behandelen ze als zwarte dozen. Dit artikel legt uit hoe afhankelijkheden werken in Makefiles, hoe ze automatisch te genereren, en hoe ze te structureren voor betrouwbaarheid en snelheid.
Wat zijn Makefiles?
Een Makefile is een tekstbestand dat een set regels definieert voor het bouwen van een project. Het -hulpprogramma leest deze regels, controleert de tijdstempels van bestanden en voert alleen de commando's uit die nodig zijn om het project up-to-date te brengen. Het kernidee is eenvoudig: elke regel heeft een target[ (het bestand dat moet worden geproduceerd), prerequisites[ (bestanden die nodig zijn om het doel te bouwen), en ]recepten[ (shell commando's die moeten draaien). Door het opnemen van welke bestanden afhankelijk zijn van welke, [ make[ kan incrementeel alleen de onderdelen herbouwen die veranderd zijn.
Makefiles zijn onderdeel van Unix sinds de jaren 1970, en GNU Make is de feitelijke standaard op Linux en macOS. De syntaxis is beknopt maar kan subtiel zijn; afhankelijkheden juist krijgen is de primaire vaardigheid die een C-ontwikkelaar nodig heeft.
Afhankelijkheden begrijpen
In een C-project zijn afhankelijkheden niet beperkt tot bestanden. Elk bronbestand bevat één of meerdere headerbestanden (bijv. ). Als een header wordt gewijzigd, moeten alle bestanden die het bevatten worden heropgestapeld. Evenzo zijn objectbestanden afhankelijk van hun overeenkomstige bestanden, en het uiteindelijke uitvoerbare bestand is afhankelijk van alle objectbestanden.
Expliciet vs Impliciete afhankelijkheden
In vroege Makefiles hebben programmeurs alle voorwaarden handmatig opgesomd. Die aanpak is kwetsbaar: een header vergeten betekent oude bouwsels, terwijl er teveel triggers worden genoemd onnodige recompiles. Erger nog, naarmate het project groeit, worden handmatige lijsten onhoudbaar. De moderne oplossing is om de compiler automatisch de afhankelijkheden te laten genereren, waardoor ze in impliciete, machine-gecontroleerde voorwaarden worden omgezet.
Een goed gebouwde Makefile behandelt afhankelijkheden als een eersteklas probleem. Het doel is nooit iets te herbouwen dat niet opnieuw hoeft te worden opgebouwd en altijd alles wat dat doet opnieuw op te bouwen. Dit is de essentie van correctheid en efficiëntie] in incrementele bouw.
Basisstructuur van een Makefile
Een typisch Makefile bevat variabele definities, regels en nepdoelen. Hier is een minimaal maar functioneel voorbeeld voor een project met en :
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
Deze Makefile heeft vier doelen: [ (het uitvoerbare bestand), twee objectbestanden en een nepdoel . De afhankelijkheidslijnen na de dubbele punt vertellen maken welke bestanden te controleren zijn voordat wordt besloten het doel opnieuw op te bouwen.
Phoenix targets
Doelen als of vertegenwoordigen geen bestanden. Om te voorkomen dat maken ze verwarren met bestandsnamen, moeten ze worden aangegeven als fonie:
.PHONY: clean all
Zonder dit zou een bestand met de naam make het actueel vinden en het recept overslaan.
Doeltreffend beheer van afhankelijkheden
De handleiding Makefile hierboven heeft een ernstige fout: de afhankelijkheid van op is correct, maar hoe zit het met headers? Als [ verandert, (waarin het is opgenomen) moet worden herbouwd, maar de regel zegt dat het alleen afhankelijk is van ]. De fix is om de compiler de echte afhankelijkheidslijst te laten produceren.
Automatische afhankelijkheidsgeneratie met GCC
GCC (en Clang) kan afhankelijkheidsinformatie genereren met behulp van de vlaggenfamilie. De meest praktische combinatie voor de meeste projecten is :
- . . . geeft de naam van het afhankelijkheidsbestand aan.
Hier vindt u hoe u automatische afhankelijkheidstracking in een Makefile kunt integreren:
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
Toelichting:
- verzamelt alle bestanden in de bronmap.
- transformeert ze in objectbestandspaden.
- geeft een lijst van de afhankelijkheidsbestanden (bv. ).
- De patroonregel compileert elk bestand en genereert, vanwege ], een bestand als bijwerking.
- De regel leest de gegenereerde bestanden, waardoor ze in echte Makefile voorwaarden worden omgezet. Het dashboard () onderdrukt fouten wanneer de bestanden nog niet bestaan (bijv. bij de eerste build).
Als wordt gewijzigd, zal de volgende aanroeping van automatisch opnieuw compileren omdat het bestand voor [ als voorwaarde bevat.
Geavanceerde technieken
Gegenereerde afhankelijkheden veilig hanteren
Wanneer een header wordt verwijderd, kan het bestand er nog steeds naar verwijzen, waardoor make niet lukt met een ontbrekende doelfout. De vlag adresseert dit door lege nepregels toe te voegen voor elke afhankelijkheidskop. Als de header is verdwenen, make] draait gewoon de nepregel (die niets doet) en gaat verder.
Inclusief afhankelijkheid bestanden na bron wijzigingen
Eén subtiliteit: als een bronbestand een toevoegt of verwijdert, moet het bijbehorende bestand worden geregenereerd. Omdat het bestand zelf een voorwaarde is voor het objectbestand, zal make het gewijzigde tijdstempel opmerken en opnieuw compileren, dat het bestand regenereert. Deze recursie werkt automatisch zodra de eerste build voltooid is.
Alleen de vereisten van de bestelling gebruiken
Soms moet je een directory hebben om te bouwen, maar je wilt niet dat de tijdstempel een heropbouw veroorzaakt. Dat is de rol van order-only prequirements (gescheiden door ]). In de patroonregel hierboven hebben we gebruikt in het recept; een alternatief is:
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR):
mkdir -p $@
Dit zorgt ervoor dat de directory wordt aangemaakt voordat een compilatie wordt gemaakt, maar een wijziging in de directory zelf (bijvoorbeeld een nieuw bestand binnen) zal geen repilatie veroorzaken.
Beste praktijken voor het beheer van afhankelijkheden
- Gebruik automatische afhankelijkheidsgeneratie vanaf dag één. Zelfs voor een enkel bestandsproject is het een goede gewoonte. Het kost niets en voorkomt toekomstige fouten.
- Houd de afhankelijkheidsbestanden gescheiden van bronbestanden. Zet ze in een of directory. Dit maakt het schoonmaken makkelijker en voorkomt het rommelen van de bronboom.
- Inclusief de gegenereerde bestanden na de regels die ze maken.[ De richtlijn is prima, maar het plaatsen ervan na de patroonregel zorgt ervoor dat maken ] eerst weet hoe objectbestanden te bouwen voordat u probeert de bestanden te lezen.
- Gebruik nepdoelen voor huishoudelijke uitgaven. , , , en zijn gebruikelijk. Ze altijd aangeven met .
- Door middel van hefboomvariabelen voor compilervlaggen, bronmappen en lijsten van bestanden.[ Dit maakt de Makefile herbruikbaar voor alle projecten en gemakkelijker aan te passen.
- Proef uw Makefile met een bewuste headerwijziging.[ Wijzig een header, voer uit en bevestig dat alleen de bestanden van het aangetaste object worden gereconstrueerd. Als een volledige heropbouw plaatsvindt, is er iets mis met het tracken van afhankelijkheid.
- Houd de Makefile eenvoudig maar niet eenvoudiger dan nodig. Over-engineering met geavanceerde functies zoals kan het debuggen pijnlijk maken. Begin met het patroon dat in dit artikel wordt getoond; het schalen goed tot tientallen bestanden.
Externe middelen
Om uw begrip te verdiepen, raadpleeg deze gezaghebbende referenties:
- GNU Make Manual
- GCC-preprocessoropties . Documentatie voor , , , en daarmee verband houdende vlaggen.
- Makefile Tutorial by Chase Lambert .Een praktische, goed gestructureerde tutorial die veel real-world use cases bestrijkt.
Conclusie
Het beheren van C programma afhankelijkheden met Makefiles is geen luxe maar een noodzaak voor elk project dat een enkel bestand overgroeit. Door patroonregels te combineren, automatische afhankelijkheidsgeneratie met , en zorgvuldige inclusie van de gegenereerde ] bestanden, kunt u een bouwsysteem maken dat zowel snel als correct is. De technieken die hier worden getoond elimineren handmatige tracking, verminderen bouwtijden, en subtiele bugs veroorzaakt door oude objectbestanden voorkomen. Zodra u deze patronen internaliseren, zult u nooit meer een handmatige afhankelijkheidslijst schrijven. De investering in het leren van de juiste afhankelijkheidsbeheer betaalt elke keer dat u draait en alleen de gewijzigde bestanden compileert.