Εισαγωγή

Στον προγραμματισμό C, ένα πρόγραμμα συχνά χωρίζεται σε πολλαπλά αρχεία πηγαίου κώδικα και αρχεία κεφαλίδων για τη βελτίωση της οργάνωσης, επαναχρησιμοποίησης και ταχύτητας συλλογής. Ωστόσο, αυτή η αρθρωτή δυνατότητα εισάγει μια πρόκληση: όταν ένα αρχείο κεφαλίδας αλλάζει, κάθε πηγαίο αρχείο που περιλαμβάνει πρέπει να επανασυνδεθεί. Κάνοντας αυτό το πρόβλημα χειροκίνητα είναι σφάλμα ⁇ εποχές και χρόνος ⁇ συνεχίζοντας. Makefiles, οδηγείται από το make[ χτίστε το εργαλείο αυτοματισμού, λύστε αυτό το πρόβλημα κωδικοποιώντας εξαρτήσεις και αυτοματοποιώντας τη διαδικασία κατασκευής. Mastering Makefiles είναι μια τελετή μετάβασης για κάθε σοβαρό δημιουργό C, αλλά πολλοί νεοφερμένοι τους αντιμετωπίζουν ως μαύρα κουτιά. Αυτό το άρθρο εξηγεί πώς οι εξαρτήσεις λειτουργούν στα Makefiles, πώς να τα δημιουργήσετε αυτόματα, και πώς να διαμορφώσετε ένα Makefile για την αξιοπιστία και την ταχύτητα.

Τι Είναι τα Makefiles;

Ένα αρχείο Makefile είναι ένα απλό αρχείο κειμένου που ορίζει ένα σύνολο κανόνων για την κατασκευή ενός έργου. Το make[] χρησιμοποιεί αυτούς τους κανόνες, ελέγχει τις χρονικές ενδείξεις αρχείων και εκτελεί μόνο τις εντολές που απαιτούνται για την ενημέρωση του έργου. Η βασική ιδέα είναι απλή: κάθε κανόνας έχει έναν στόχο (το αρχείο για την παραγωγή), προαπαιτείται (αρχεία που απαιτούνται για την κατασκευή του στόχου), και τα recipes (οχύλι εντολές για να εκτελεστούν). Με την καταγραφή των αρχείων που εξαρτώνται από τα οποία, make μπορούν να ανοικοδομήσουν σταδιακά μόνο τα τμήματα που έχουν αλλάξει.

Τα Makefiles είναι μέρος του Unix από τη δεκαετία του 1970, και το GNU Make είναι το de facto πρότυπο για το Linux και το macOS. Η σύνταξη είναι συνοπτική αλλά μπορεί να είναι λεπτή.

Κατανόηση εξαρτήσεων

Σε ένα έργο C, οι εξαρτήσεις δεν περιορίζονται σε αρχεία [[LFT:0]]. Κάθε αρχείο πηγής περιλαμβάνει ένα ή περισσότερα αρχεία κεφαλίδας (π.χ., [[LFT:1]]). Αν μια κεφαλίδα τροποποιηθεί, όλα τα αρχεία [[LFT:2] που περιλαμβάνουν αυτό πρέπει να επανασυνδεθούν. Ομοίως, τα αρχεία αντικειμένων εξαρτώνται από τα αντίστοιχα αρχεία [[LFT:3]] και το τελικό εκτελέσιμο εξαρτάται από όλα τα αρχεία αντικειμένων.

Explicit vs Έμμεσες εξαρτήσεις

Σε πρώιμα Makefiles, οι προγραμματιστές που αναφέρονται όλες τις προϋποθέσεις χειροκίνητα. Αυτή η προσέγγιση είναι εύθραυστη: η λησμονώντας μια κεφαλίδα σημαίνει μπαγιάτικη κατασκευή, ενώ η απαρίθμηση πάρα πολλών ενεργοποιεί περιττές επανασυναρμολογήσεις. Χειρότερα, καθώς το έργο μεγαλώνει, οι χειροκίνητες λίστες γίνονται μη διατηρητέες. Η σύγχρονη λύση είναι να έχει ο μεταγλωττιστής παράγει τις εξαρτήσεις αυτόματα, μετατρέποντάς τους σε σιωπηρές, μηχανο-ελέγχους προϋποθέσεις.

Ένα καλά κατασκευασμένο Makefile αντιμετωπίζει εξαρτήσεις ως πρώτη θέση ανησυχία. Ο στόχος είναι να μην ξαναχτιστεί ποτέ τίποτα που δεν χρειάζεται ανοικοδόμηση, και να ξαναχτιστεί πάντα ό, τι κάνει. Αυτή είναι η ουσία ορθότητα και αποδοτικότητα[] σε στοιχειώδη κτίρια.

Βασική δομή ενός αρχείου MakeName

Ένα τυπικό Makefile περιέχει μεταβλητούς ορισμούς, κανόνες και ψεύτικους στόχους. Εδώ είναι ένα ελάχιστο αλλά λειτουργικό παράδειγμα για ένα έργο με και :

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

Αυτό το Makefile έχει τέσσερις στόχους: (το εκτελέσιμο), δύο αρχεία αντικειμένων, και έναν ψεύτικο στόχο . Οι γραμμές εξάρτησης μετά το κόλον λένε ] κάνουν[] τα αρχεία που πρέπει να ελέγξουν πριν αποφασίσουν να ξαναχτίσουν τον στόχο.

Ψεύτικοι Στόχοι

Στόχοι όπως ή δεν αντιπροσωπεύουν αρχεία. Για να αποτρέψουν ] να κάνουν σύγχυση με τα ονόματα αρχείων, θα πρέπει να δηλωθούν ως φωνή[:

.PHONY: clean all

Χωρίς αυτό, αν υπήρχε ένα αρχείο με το όνομα , θα το θεωρούσαν ως μέχρι ⁇ ημερομηνία και παραλείψτε τη συνταγή.

Διαχείριση Εξαρτήσεων Αποτελεσματικά

Το εγχειρίδιο Makefile παραπάνω έχει ένα σοβαρό ελάττωμα: η εξάρτηση από [[LFT:14]] είναι σωστή, αλλά τι γίνεται με τις κεφαλίδες; Αν αλλάξει, (που περιλαμβάνει αυτό) πρέπει να ξαναχτιστεί, ωστόσο ο κανόνας λέει ότι εξαρτάται μόνο από [[LFT:17]]]. Η λύση είναι να αφήσουμε τον μεταγλωττιστή να παράγει τον πραγματικό κατάλογο εξάρτησης.

Αυτόματη παραγωγή εξάρτησης με το GCC

Το GCC (και το Clang) μπορούν να δημιουργήσουν πληροφορίες εξάρτησης χρησιμοποιώντας την οικογένεια σημαιών. Ο πιο πρακτικός συνδυασμός για τα περισσότερα έργα είναι :

  • ⁇ γράφει ένα αρχείο εξάρτησης () κατά τη διάρκεια της συλλογής, απαριθμώντας μόνο τις κεφαλίδες που ορίζονται από το χρήστη (όχι τις κεφαλίδες συστήματος).
  • ⁇ προσδιορίζει το όνομα του αρχείου εξάρτησης.
  • ⁇ προσθέτει ψεύτικους στόχους για κάθε κεφαλίδα, αποτρέποντας σφάλματα όταν αφαιρείται μια κεφαλίδα.

Εδώ είναι πώς να ενσωματώσει αυτόματη παρακολούθηση εξάρτηση σε ένα 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

Επεξήγηση:

  • συλλέγει όλα τα αρχεία στον κατάλογο πηγών.
  • τα μετατρέπει σε διαδρομές αρχείων αντικειμένων.
  • παραθέτει τα αρχεία εξάρτησης (π.χ., ).
  • Ο κανόνας μοτίβο συγκεντρώνει το καθένα αρχείο και, λόγω , παράγει ένα αρχείο ως παρενέργεια.
  • Η γραμμή διαβάζει τα αρχεία που παράγονται , μετατρέποντάς τα σε πραγματικές προϋποθέσεις Makefile. Η παύλα () καταστέλλει τα λάθη όταν τα αρχεία δεν υπάρχουν ακόμα (π.χ., στην πρώτη κατασκευή).

Τώρα, εάν [[LFT:38]] τροποποιηθεί, η επόμενη επίκληση του [[LFT:39]] θα ανασυνταχθεί [[[LFT:40]] αυτόματα επειδή ο [[LFT:41]] φάκελος για [[LFT:42]] περιλαμβάνει [[LFT:43]]] ως προϋπόθεση.

Προχωρημένες Τεχνικές

Χειρισμός Δημιουργημένων Εξαρτήσεων Ασφαλώς

Όταν μια κεφαλίδα διαγράφεται, το [[LFT:44]] αρχείο μπορεί ακόμα να το αναφέρει, προκαλώντας [[LFT:0]]] να αποτύχει[[[LFT:1]] με ένα σφάλμα στόχου που λείπει. Η [[LFT:45]] σημαία το αντιμετωπίζει αυτό προσθέτοντας άδειους ψεύτικους κανόνες για κάθε κεφαλίδα εξάρτησης. Αν η κεφαλίδα έχει φύγει, [[LFT:2]] να κάνει[ απλά τρέχει τον ψεύτικο κανόνα (που δεν κάνει τίποτα) και συνεχίζει.

Συμπεριλαμβανομένων αρχείων εξάρτησης μετά τις αλλαγές πηγαίου κώδικα

Μια λεπτότητα: αν ένα αρχείο πηγής προσθέσει ή αφαιρέσει ένα , το αντίστοιχο αρχείο [[LFT:47]] πρέπει να αναγεννηθεί. Επειδή το αρχείο [[LFT:48]] είναι η ίδια προϋπόθεση του αρχείου αντικειμένου, [[LFT:0]]] θα κάνει[[[LFT:1]]] θα παρατηρήσει την αλλαγή χρονοσφραγίδα και την ανατροφοδότηση, η οποία αναγεννά το αρχείο []. Αυτή η αναδρομή λειτουργεί αυτόματα μόλις ολοκληρωθεί η πρώτη κατασκευή.

Χρήση της τάξης ⁇ Μόνο τα προαπαιτούμενα

Μερικές φορές χρειάζεστε έναν κατάλογο για να υπάρχει πριν από την κατασκευή, αλλά δεν θέλετε το timestamp του να ενεργοποιήσει μια ανοικοδόμηση. Αυτός είναι ο ρόλος του order ⁇ μόνο προϋποθέσεις (χωρίζεται από []]). Στον παραπάνω κανόνα μοτίβο, χρησιμοποιήσαμε [[LFT:51]] μέσα στη συνταγή; μια εναλλακτική είναι:

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

$(OBJDIR):
 mkdir -p $@

Αυτό εξασφαλίζει ότι ο κατάλογος δημιουργείται πριν από οποιαδήποτε συλλογή, αλλά μια αλλαγή στον ίδιο τον κατάλογο (π.χ., ένα νέο αρχείο μέσα) δεν θα ενεργοποιήσει την επαναπροσαρμογή.

Βέλτιστες πρακτικές για τη διαχείριση εξαρτήσεων

  1. Χρησιμοποιήστε την αυτόματη γενιά εξάρτησης από την πρώτη ημέρα. Ακόμα και για ένα έργο ενός αρχείου, είναι μια καλή συνήθεια.
  2. Κρατήστε τα αρχεία εξάρτησης χωριστά από τα αρχεία πηγής. Βάλτε τα σε έναν κατάλογο ή . Αυτό καθιστά τον καθαρισμό ευκολότερο και αποφεύγει τη συσπείρωση του δέντρου πηγής.
  3. Περιλάβετε τα αρχεία που παράγονται μετά τους κανόνες που τα δημιουργούν. Η οδηγία είναι μια χαρά, αλλά η τοποθέτηση της μετά τον κανόνα μοτίβο εξασφαλίζει ότι [] κάνει[ πρώτα ξέρει πώς να κατασκευάσει αρχεία αντικειμένων πριν προσπαθήσει να διαβάσει τα αρχεία .
  4. Χρησιμοποιήστε ψεύτικους στόχους για την καθαριότητα. , , ], και είναι κοινά. Πάντα τα δηλώνετε με .
  5. Μεταβλητές μεταγλωττιστών σημαιών, καταλόγων πηγών και καταλόγων αρχείων. Αυτό καθιστά το Makefile επαναχρησιμοποιήσιμο σε διάφορα έργα και ευκολότερο να προσαρμόσετε.
  6. Δοκιμάστε το Makefile σας με μια σκόπιμη αλλαγή κεφαλίδας. Τροποποίηση κεφαλίδας, εκτελέστε ], και επιβεβαιώστε ότι μόνο τα αρχεία αντικειμένων που επηρεάζονται αποζημιώνονται. Αν μια πλήρης ανακατασκευή συμβεί, κάτι δεν πάει καλά με την παρακολούθηση εξάρτησης.
  7. Κρατήστε το Makefile απλό αλλά όχι απλούστερο από ό, τι απαιτείται. Πάνω ⁇ μηχανική με προηγμένες λειτουργίες όπως μπορεί να κάνει την αποσφαλμάτωση επώδυνη. Ξεκινήστε με το μοτίβο που εμφανίζεται σε αυτό το άρθρο.

Εξωτερικοί πόροι

Για να εμβαθύνετε την κατανόησή σας, συμβουλευτείτε αυτές τις έγκυρες αναφορές:

  • GNU Make Manual ⁇ Ο οριστικός οδηγός για τη σύνταξη, τις λειτουργίες και τα προηγμένα χαρακτηριστικά.
  • GCC Επιλογές προεπεξεργαστή ⁇ Τεκμηρίωση για , , ] και σχετικές σημαίες.
  • Makefile Tutorial by Chase Lambert ⁇ Ένα πρακτικό, καλά δομημένο φροντιστήριο που καλύπτει πολλές περιπτώσεις πραγματικής ⁇ παγκόσμιας χρήσης.

Συμπέρασμα

Η διαχείριση των εξαρτήσεων του προγράμματος C με Makefiles δεν είναι πολυτέλεια αλλά αναγκαιότητα για οποιοδήποτε έργο ξεπερνάει ένα ενιαίο αρχείο. Συνδυάζοντας κανόνες μοτίβου, αυτόματης εξάρτησης με [[[LFT:68]]], και προσεκτική συμπερίληψη των παραγόμενων αρχείων [[LFT:69]], μπορείτε να δημιουργήσετε ένα σύστημα κατασκευής που είναι τόσο γρήγορο και σωστό. Οι τεχνικές που εμφανίζονται εδώ εξαλείφουν το χειροκίνητο εντοπισμό, μειώνουν τους χρόνους κατασκευής, και εμποδίζουν τα λεπτά σφάλματα που προκαλούνται από τα αρχεία μπαγιάδων αντικειμένων. Μόλις εσωτερικοποιήσετε αυτά τα μοτίβα, δεν θα γράψετε ποτέ ξανά μια λίστα εξάρτησης από το εγχειρίδιο. Η επένδυση στην εκμάθηση σωστής διαχείρισης εξάρτησης αποδίδει κάθε φορά που τρέχετε [[LFT:70]] και παρακολουθείτε μόνο τα αλλαγμένα αρχεία που μεταγλωττίζονται.