Οδηγός Μελέτης - Εισαγωγή στον Προγραμματισμό

Διάλεξη 26 · 19/12/2025 · Διαφάνειες (PDF) · Σημειώσεις 0 · Σημειώσεις 1 · Εργαστήριο 10

Παράρτημα Α: How to Make?

Στόχοι: μετά από αυτό το παράρτημα θα μπορείτε να ξεχωρίζετε ένα σφάλμα μεταγλώττισης από ένα σφάλμα σύνδεσης· να χτίζετε ένα project πολλών αρχείων σε βήματα (gcc -c και σύνδεση)· να εξηγείτε γιατί ένα bash script δεν αρκεί· και να γράφετε ένα Makefile με rules, macros, automatic variables, wildcard rules και built-in rules.

Προαπαιτούμενα: Κεφάλαιο 0 (preprocessor, compiler, linker), Κεφάλαιο 3 (-lm), Κεφάλαιο 23 (οργάνωση κώδικα σε αρχεία)

Χρόνος μελέτης: ~1,5 ώρα

Σύνοψη

Η προσκεκλημένη διάλεξη, από τον Πέτρο, δευτεροετή φοιτητή και βοηθό του μαθήματος, παρουσιάζει το Make, το εργαλείο που αυτοματοποιεί την κατασκευή (build) ενός προγράμματος. Ξεκινά από τη διαδικασία μετάφρασης της C (preprocessor, compiler, linker) και από το πώς ξεχωρίζουμε τα λάθη κάθε σταδίου. Μετά δείχνει γιατί, μόλις ένα project έχει πολλά αρχεία, οι εντολές gcc γίνονται μακριές, πολλές και αργές, και γιατί ένα bash script λύνει μόνο τα δύο πρώτα προβλήματα. Το Make λύνει και το τρίτο: ξέρει τι εξαρτάται από τι και ξαναχτίζει μόνο ό,τι άλλαξε. Η διάλεξη κλείνει με τα εργαλεία που κάνουν ένα Makefile σύντομο: macros, automatic variables, wildcard rules και built-in rules.

Θεωρία

§26.1 Η διαδικασία μετάφρασης της C

Ένα εκτελέσιμο δεν βγαίνει από το .c σε ένα βήμα. Κάθε αρχείο .c, που λέγεται translation unit (μονάδα μετάφρασης), περνά από τρία στάδια (Κεφάλαιο 0):

  1. ο προεπεξεργαστής (preprocessor, cpp) εκτελεί τις οδηγίες #include, #define κ.λπ. και βγάζει ένα προεπεξεργασμένο translation unit·
  2. ο μεταγλωττιστής (compiler, gcc) το μεταφράζει σε ένα object file (αντικειμενικό αρχείο, .o) σε γλώσσα μηχανής·
  3. ο συνδέτης (linker, ld) ενώνει όλα τα object files και τις δυναμικές βιβλιοθήκες (dynamic libraries, .so) σε ένα εκτελέσιμο (executable).
flowchart LR
  TU["Translation unit (.c)"] --> CPP["Preprocessor (cpp)"]
  CPP --> PTU["Προεπεξεργασμένο (.c)"]
  PTU --> CC["Compiler (gcc)"]
  CC --> O1["Object file (.o)"]
  O1 --> LD["Linker (ld)"]
  O2["Άλλα object files (.o)"] --> LD
  SO["Dynamic library (.so)"] --> LD
  LD --> EXE["Executable"]

Σχήμα: από το translation unit στο εκτελέσιμο (διαφάνεια 5).

Η εντολή gcc main.c -o main κάνει και τα τρία στάδια μαζί· το gcc στην ουσία καλεί με τη σειρά τα cpp, τον compiler και το ld.

§26.2 Σφάλματα μεταγλώττισης και σφάλματα σύνδεσης

Κάθε στάδιο έχει τα δικά του λάθη, και το να καταλαβαίνετε ποιο στάδιο παραπονιέται είναι το μισό της διόρθωσης.

Ένα αρχείο επικεφαλίδας όπως το math.h δίνει μόνο δηλώσεις. Η υλοποίηση της sqrt βρίσκεται στη μαθηματική βιβλιοθήκη του συστήματος, libm.so, και πρέπει να πείτε στον linker να τη χρησιμοποιήσει με το flag -l:libm.so, ή σύντομα -lm.

§26.3 Projects με πολλά αρχεία

Τα πραγματικά projects αποτελούνται από πολλά αρχεία .c, που πρέπει να μεταγλωττιστούν με όμοιο τρόπο και να συνδεθούν. Το project της διάλεξης έχει ένα main.c, που καλεί τη n_primes, και ένα primes.c, που την υλοποιεί. Το main.c χρειάζεται μόνο τη δήλωση ull *n_primes(ull n);· την υλοποίηση τη φέρνει ο linker από το primes.o.

Το πιο απλό είναι να δώσετε όλα τα αρχεία σε ένα gcc main.c primes.c -o main. Πίσω από αυτή τη γραμμή τρέχουν ξεχωριστά ο cpp και ο compiler για κάθε αρχείο και στο τέλος ένα ld με όλα τα .o (Παράδειγμα «Ένα project με δύο αρχεία»).

§26.4 Τρία προβλήματα του χειροκίνητου compile

Με περισσότερα από ένα αρχεία, η στρατηγική «γράφω την εντολή gcc με το χέρι» έχει τρία προβλήματα:

  1. Μακριές εντολές. Κάθε νέα απαίτηση προσθέτει flags: -Wall, -Wextra, -Werror, -std=c99, -pedantic, … και η εντολή γίνεται δύσκολη στο πληκτρολόγηση και στη μνήμη.
  2. Πολλές εντολές. Όταν ένα αρχείο θέλει διαφορετικά flags από τα άλλα, το compile πρέπει να σπάσει σε βήματα: ένα gcc -c ανά αρχείο και ένα για τη σύνδεση. Παράδειγμα: το primes.c θέλει την reallocarray, που δεν είναι στο πρότυπο της C, άρα δεν μπορεί να μεταγλωττιστεί με -std=c99 όπως το main.c.
  3. Επαναμεταγλώττιση ολόκληρου του project σε κάθε αλλαγή (βλ. παρακάτω).

Ήδη με πέντε αρχεία (η Εργασία 2, elevate) χρειάζονται έξι εντολές. Κανείς δεν τις θυμάται απ’ έξω, και σε μεγάλο project ο χρόνος να τις τρέχετε μία μία ξεπερνά τον χρόνο της ίδιας της αλλαγής στον κώδικα.

§26.5 Build scripts σε bash

Η πρώτη λύση υπάρχει από τη δεκαετία του ‘70: βάζετε όλες τις εντολές σε ένα bash script (build.sh) και τρέχετε αυτό. Το set -xe κάνει το script να τυπώνει κάθε εντολή πριν την τρέξει (-x) και να σταματά στο πρώτο λάθος (-e). Έτσι λύνονται τα προβλήματα 1 και 2: οι εντολές γράφονται μία φορά.

Το bash όμως είναι γλώσσα γενικού σκοπού, όχι φτιαγμένη για compile. Δεν ξέρει ποια αρχεία άλλαξαν, οπότε κάθε εκτέλεση ξαναμεταγλωττίζει όλο το project από την αρχή.

§26.6 Γιατί η πλήρης επαναμεταγλώττιση είναι πρόβλημα

Η GNU C Library (glibc), η πιο διαδεδομένη υλοποίηση της standard library της C (και των προτύπων POSIX, GNU C, System V κ.ά.), έχει 1.524.568 γραμμές κώδικα σε πάνω από 14.000 αρχεία. Αν προσθέσετε ένα σχόλιο σε ένα αρχείο, ένα script θα ξαναμεταγλωττίσει και τα 14.000.

Η «λύση» να ξαναμεταγλωττίζετε με το χέρι μόνο τα αρχεία που αλλάξατε και να συνδέετε με ένα script έχει μια παγίδα: τι γίνεται αν ξεχάσετε ένα; Τότε τρέχετε ένα παλιό .o και ψάχνετε ένα bug που έχετε ήδη διορθώσει. Ακριβώς αυτό οδήγησε στο Make: ο Stuart Feldman γράφει ότι ο Steve Johnson (δημιουργός του yacc) έχασε ένα πρωί αποσφαλματώνοντας ένα σωστό πρόγραμμα, επειδή η διόρθωση δεν είχε μεταγλωττιστεί. Το εργαλείο γράφτηκε εκείνο το Σαββατοκύριακο, και τα Makefiles έγιναν απλά αρχεία κειμένου, «printable, debuggable, understandable», όπως θέλει η φιλοσοφία του Unix.

§26.7 Το Make και οι βασικές έννοιες

Το Make είναι πρόγραμμα που εκτελεί εντολές με βάση προκαθορισμένες σχέσεις εξάρτησης (dependencies). Υπάρχουν πολλές παραλλαγές του· εδώ χρησιμοποιούμε το GNU Make, αλλά τα περισσότερα ισχύουν και για τις άλλες. Τρεις έννοιες:

§26.8 Το Makefile και η σύνταξη ενός rule

Ένα Makefile είναι ένα σύνολο από rules με σκοπό την παραγωγή targets:

# rule
target_name1 target_name2: prerequisite1 prerequisite2
	# recipe
	command1
	command2

Το rule λέει ότι τα target_name1 και target_name2 φτιάχνονται τρέχοντας τα command1 και command2 σε shell, και ότι για να φτιαχτούν πρέπει πρώτα να έχουν φτιαχτεί τα prerequisite1 και prerequisite2.

Προσοχή στο indentation: ό,τι είναι indented, το Make το θεωρεί μέρος του recipe. Στο GNU Make κάθε γραμμή του recipe ξεκινά με tab, όχι με κενά (έτσι το γράφει και το Εργαστήριο 10).

§26.9 Η εντολή make

Αν στον τρέχοντα φάκελο υπάρχει αρχείο με όνομα Makefile (ή ένα από λίγα άλλα αναγνωρισμένα ονόματα, όπως makefile), τρέχετε:

make target_name

Το make προσπαθεί να κατασκευάσει το target_name ακολουθώντας το τελευταίο rule του Makefile που ορίζει recipe για αυτό το όνομα. Χωρίς όρισμα, make σκέτο, χτίζει το target του πρώτου rule του αρχείου. Γι’ αυτό το πρώτο rule είναι συνήθως το τελικό εκτελέσιμο.

§26.10 Ο αλγόριθμος κατασκευής

Αφού επιλεγεί το build target, το Make ακολουθεί περίπου τον εξής αναδρομικό αλγόριθμο (διαφάνεια 28):

proc Make-Target(target):
    foreach prerequisite of target:
        if prerequisite is out of date:
            mark target out of date
            Make-Target(prerequisite)

    if target is out of date:
        run recipe of target
endproc

Ένα target είναι out of date (παρωχημένο) όταν δεν υπάρχει, ή όταν κάποιο prerequisite του είναι νεότερο από αυτό: το Make συγκρίνει τους χρόνους τελευταίας τροποποίησης των αρχείων. Οι εξαρτήσεις σχηματίζουν ένα δέντρο (γενικότερα, γράφο):

flowchart TD
  M["main"] --> MO["main.o"]
  M --> PO["primes.o"]
  MO --> MC["main.c"]
  MO --> PH["primes.h"]
  PO --> PH
  PO --> PC["primes.c"]

Σχήμα: οι εξαρτήσεις του project main (διαφάνεια 29).

Αν αλλάξετε το main.c, μόνο το main.o και το main είναι out of date· το primes.o δεν ξαναφτιάχνεται. Αν αλλάξετε το primes.h, ξαναφτιάχνονται και τα δύο .o, γιατί και τα δύο το κάνουν #include. Το Make δεν μπορεί να «ξεχάσει» ένα αρχείο, όπως εσείς: αρκεί οι εξαρτήσεις να είναι σωστά δηλωμένες.

§26.11 Macros

Αν κάποιος θέλει να χτίσει το project με clang αντί για gcc (π.χ. σε Mac), πρέπει να αλλάξει κάθε εμφάνιση του gcc στο Makefile. Η λύση είναι η ίδια με της C: μια μεταβλητή. Στο Make οι μεταβλητές λέγονται macros και περιέχουν strings:

MACRO_NAME <assignment-operator> string value

Τα ονόματα γράφονται συνήθως με κεφαλαία, και η τιμή είναι string χωρίς εισαγωγικά. Χρησιμοποιείτε ένα macro οπουδήποτε στο Makefile γράφοντας $(MACRO_NAME). Η τελική τιμή ενός macro είναι η τελευταία που του ανατέθηκε μέσα στο Makefile (ή μέσα σε ένα rule).

Υπάρχουν δύο είδη ανάθεσης:

Τελεστής Τι κάνει
= lazy ανάθεση
:= eager ανάθεση
?= lazy ανάθεση, μόνο αν το macro δεν έχει ήδη τιμή
+= προσθέτει τη δεξιά μεριά στο τέλος της αριστερής (lazily)

Η διαφορά = και := φαίνεται όταν το δεξί μέλος χρησιμοποιεί ένα macro που ορίζεται αργότερα:

A = $(B)
C := $(B)
B = hello

Εδώ το $(A) δίνει hello (αποτιμάται στη χρήση, όταν το B έχει ήδη τιμή), ενώ το $(C) είναι κενό (αποτιμήθηκε όταν το B δεν υπήρχε ακόμη).

§26.12 Automatic variables

Μέσα σε ένα recipe μπορείτε να χρησιμοποιήσετε και automatic variables, μεταβλητές που αλλάζουν αυτόματα τιμή από rule σε rule:

Μεταβλητή Τιμή
$@ το όνομα του target που φτιάχνεται τώρα
$^ η λίστα όλων των prerequisites
$< το πρώτο prerequisite

Έτσι ένα recipe δεν επαναλαμβάνει ονόματα αρχείων: $(CC) -o $@ $^ σημαίνει «σύνδεσε όλα τα prerequisites στο target».

§26.13 Wildcard rules

Με macros και automatic variables, τα rules για main.o και primes.o γίνονται σχεδόν πανομοιότυπα. Όπως στον προγραμματισμό, όταν κάτι επαναλαμβάνεται, το αφαιρούμε (to abstract it). Ένα wildcard rule (στο εγχειρίδιο του GNU Make: pattern rule) ορίζει με τον ειδικό χαρακτήρα % μια ολόκληρη οικογένεια από rules:

%.o: %.c
	recipe

Αυτό λέει ότι κάθε X.o έχει prerequisite το αντίστοιχο X.c και φτιάχνεται με το ίδιο recipe. Μέσα στο recipe, το $< είναι το X.c και το $@ το X.o.

§26.14 Built-in rules

Το Make φτιάχτηκε για να χτίζει projects σε C, οπότε έχει ήδη ορισμένα built-in rules για τα πιο συνηθισμένα:

Τα built-in rules χρησιμοποιούν γνωστά macros, όπως το CC (ο compiler) και το CFLAGS (τα flags του compiler). Ορίζοντας μόνο αυτά και τις εξαρτήσεις, το Makefile του project γίνεται 2–3 γραμμές (Παράδειγμα «Makefile με built-in rules»).

§26.15 Το Make στον πραγματικό κόσμο

Η απλότητα και η δύναμη του Make το πάνε πολύ μακριά. Ο Linux kernel (~35 εκατομμύρια γραμμές) χτίζεται με Make. Τα πιο προχωρημένα εργαλεία (cmake, premake, automake, autoconf) είναι χτισμένα πάνω στο Make και το χρησιμοποιούν, οπότε όλα τα μεγάλα projects χρησιμοποιούν Make, άμεσα ή έμμεσα. Και το Make δεν είναι δεμένο με τη C: δουλεύει και για άλλες γλώσσες.

Παραδείγματα

§26.16 Το math.h και η libm.so

Εφαρμόζει: «Σφάλματα μεταγλώττισης και σφάλματα σύνδεσης».

Ξεκινάμε χωρίς #include <math.h>:

#include <stdio.h>
// does-not-compile
int main(void) {
    printf("%.2f\n", sqrt(3));
    return 0;
}
$ gcc -o main main.c
main.c: In function 'main':
main.c:4:20: error: implicit declaration of function 'sqrt'
[-Wimplicit-function-declaration]
    4 |   printf("%.2f\n", sqrt(3));
      |                     ^~~~
main.c:2:1: note: include '<math.h>' or provide a declaration of 'sqrt'
    1 | #include <stdio.h>
  +++ |+#include <math.h>
    2 |

Είναι compiler error: ο compiler δεν βρίσκει τη δήλωση της sqrt στο αρχείο, και μας λέει ακριβώς πώς να το διορθώσουμε. Προσθέτουμε #include <math.h>:

$ gcc -o main main.c
/usr/bin/ld: /tmp/ccaw9D8e.o: in function `main':
main.c:(.text+0x1f): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status

Τώρα είναι linking error (το λέει το ld returned 1): η δήλωση υπάρχει, η υλοποίηση όχι. Δίνουμε στον linker τη βιβλιοθήκη:

$ gcc -o main main.c -l:libm.so # or -lm for short
$ ./main
1.73

§26.17 Ένα project με δύο αρχεία

Εφαρμόζει: «Projects με πολλά αρχεία», «Τρία προβλήματα του χειροκίνητου compile».

Τα δύο αρχεία της διαφάνειας 10 (το σώμα των συναρτήσεων παραλείπεται στις διαφάνειες):

/* file: main.c */
typedef unsigned long long ull;
ull *n_primes(ull n);

int main(int argc, const char **argv) {
....
    ull *primes = n_primes(n);
....
}
/* file: primes.c */
typedef unsigned long long ull;
ull *n_primes(ull n) {
....
}

Η εύκολη και η «πλήρης» εκδοχή του ίδιου build:

$ gcc main.c primes.c -o main
$ ./main 21
Found primes: 2 3 5 7 11 13 17 19
$ cpp main.c -o main.C
$ cpp primes.c -o primes.C
$ gcc main.C -c
$ gcc primes.C -c
$ ld -o main --dynamic-linker /lib64/ld-linux-x86-64.so.2 \
    /lib/crt1.o /lib/crti.o main.o primes.o -lc /lib/crtn.o
$ ./main 21
Found primes: 2 3 5 7 11 13 17 19

Η δεύτερη δείχνει τι κάνει το gcc για εμάς: προεπεξεργασία, compile κάθε αρχείου, και σύνδεση με τη standard library (-lc) και τα αρχεία εκκίνησης (crt*.o).

Όταν τα αρχεία θέλουν διαφορετικά flags (το primes.c χωρίς -std=c99, για την reallocarray), το build σπάει σε βήματα:

$ gcc -Wall -Wextra -Werror -std=c99 -pedantic -c main.c
$ gcc -Wall -Wextra -Werror -pedantic -c primes.c
$ gcc -o main primes.o main.o

§26.18 Το build της Εργασίας 2 (elevate)

Εφαρμόζει: «Τρία προβλήματα του χειροκίνητου compile».

Η Εργασία 2 του 2025-26 (elevate) χτίζεται με έξι εντολές, όπως τις δίνει η εκφώνηση:

$ gcc -Os -c -Wall -Wextra -Werror -pedantic recurse.c
$ gcc -Os -c -Wall -Wextra -Werror -pedantic brute.c
$ gcc -Os -c -Wall -Wextra -Werror -pedantic memoize.c
$ gcc -Os -c -Wall -Wextra -Werror -pedantic dp.c
$ gcc -Os -c -Wall -Wextra -Werror -pedantic elevate.c
$ gcc -Os -o elevate recurse.o brute.o memoize.o dp.o elevate.o

Είναι το ιδανικό πρώτο Makefile σας (βλ. «Makefile με built-in rules»).

§26.19 Το build.sh

Εφαρμόζει: «Build scripts σε bash».

#!/usr/bin/env bash
# file: build.sh
set -xe
gcc -Wall -Werror -Wextra -pedantic -std=c99 -c main.c
gcc -Wall -Werror -Wextra -pedantic -c primes.c
gcc -o main main.o primes.o
$ chmod +x ./build.sh
$ ./build.sh 2> /dev/null
$ ./main 42
Found primes: 2 3 5 7 11 13 17 19 23 29 31 37 41

Το 2> /dev/null κρύβει την έξοδο του set -x, που γράφεται στο stderr. Το script ξαναμεταγλωττίζει και τα δύο αρχεία σε κάθε εκτέλεση, όποιο κι αν άλλαξε.

§26.20 Το πρώτο Makefile

Εφαρμόζει: «Το Makefile και η σύνταξη ενός rule», «Ο αλγόριθμος κατασκευής».

Το Makefile της διαφάνειας 31 γράφει το δέντρο εξαρτήσεων ως τρία rules, με το αρχείο επικεφαλίδας primes.h (όπου πηγαίνουν πλέον το typedef και η δήλωση της n_primes) ως prerequisite και των δύο .o:

main: main.o primes.o
	gcc -o main main.o primes.o

main.o: main.c primes.h
	gcc -Wall -Wextra -Werror -pedantic -std=c99 -c main.c

primes.o: primes.c primes.h
	gcc -Wall -Wextra -Werror -pedantic -c primes.c

Μια συνεδρία (τα μηνύματα είναι του GNU Make 4):

$ make
gcc -Wall -Wextra -Werror -pedantic -std=c99 -c main.c
gcc -Wall -Wextra -Werror -pedantic -c primes.c
gcc -o main main.o primes.o
$ make
make: 'main' is up to date.
$ touch main.c
$ make
gcc -Wall -Wextra -Werror -pedantic -std=c99 -c main.c
gcc -o main main.o primes.o

Μετά το touch main.c το primes.o δεν ξαναφτιάχτηκε. Λειτουργεί· είναι καλό; Όχι ακόμη: το gcc και τα flags επαναλαμβάνονται παντού.

§26.21 Makefile με macros, automatic variables και wildcard rule

Εφαρμόζει: «Macros», «Automatic variables», «Wildcard rules».

Οι διαφάνειες 32–38 περιγράφουν τα βήματα χωρίς να δείχνουν το τελικό αρχείο. Μια εκδοχή που τα εφαρμόζει όλα:

CC = gcc
CFLAGS = -Wall -Wextra -Werror -pedantic

main: main.o primes.o
	$(CC) -o $@ $^

main.o: CFLAGS += -std=c99

%.o: %.c primes.h
	$(CC) $(CFLAGS) -c $<
$ make CC=clang
clang -Wall -Wextra -Werror -pedantic -std=c99 -c main.c
clang -Wall -Wextra -Werror -pedantic -c primes.c
clang -o main main.o primes.o

§26.22 Makefile με built-in rules

Εφαρμόζει: «Built-in rules».

Αφού το Make ξέρει ήδη πώς φτιάχνεται ένα .o από ένα .c και ένα εκτελέσιμο από .o, αρκεί να δηλώσουμε τα flags και τις εξαρτήσεις:

CFLAGS = -Wall -Wextra -Werror -pedantic
main: main.o primes.o
main.o primes.o: primes.h
$ make
cc -Wall -Wextra -Werror -pedantic   -c -o main.o main.c
cc -Wall -Wextra -Werror -pedantic   -c -o primes.o primes.c
cc   main.o primes.o   -o main

Το built-in rule χρησιμοποιεί $(CC), που από προεπιλογή είναι cc (στο Linux συνήθως το gcc). Ένα rule χωρίς recipe, όπως το main.o primes.o: primes.h, απλώς προσθέτει prerequisites.

§26.23 Συμβουλή για το Εργαστήριο 10

Το Εργαστήριο 10, ενότητα «Αυτοματοποίηση με make», χτίζει το collatz με ένα Makefile με CC, CFLAGS και έναν στόχο clean (rm -f collatz main.o collatz.o). Στην Άσκηση 5 γράφετε το δικό σας Makefile για το more.c: επιβεβαιώστε, αλλάζοντας μόνο το main.c, ότι το make ξαναμεταγλωττίζει μόνο αυτό.

Κύρια σημεία

  1. Κάθε .c (translation unit) περνά από τον preprocessor και τον compiler και γίνεται object file· ο linker ενώνει τα object files και τις βιβλιοθήκες σε εκτελέσιμο.
  2. Ένα compiler error αφορά το translation unit (π.χ. λείπει μια δήλωση)· ένα linking error σημαίνει ότι λείπει μια υλοποίηση από το linking scope.
  3. Το #include <math.h> δίνει μόνο δηλώσεις· την υλοποίηση της sqrt τη φέρνει το -lm (libm.so).
  4. Σε projects πολλών αρχείων το χειροκίνητο compile έχει τρία προβλήματα: μακριές εντολές, πολλές εντολές και επαναμεταγλώττιση όλου του project.
  5. Ένα bash script λύνει τα δύο πρώτα, αλλά ξαναμεταγλωττίζει τα πάντα σε κάθε εκτέλεση· αν πάλι ξαναμεταγλωττίζετε με το χέρι, κάποια στιγμή θα ξεχάσετε ένα αρχείο.
  6. Το Make εκτελεί recipes με βάση σχέσεις εξάρτησης και ξαναχτίζει μόνο τα targets που είναι out of date.
  7. Ένα rule γράφεται target: prerequisites και από κάτω το recipe, με κάθε γραμμή indented με tab.
  8. make target χτίζει το συγκεκριμένο target· make σκέτο χτίζει το target του πρώτου rule.
  9. Τα macros (CC, CFLAGS) κρατούν strings· το = αποτιμάται στη χρήση, το := στην ανάθεση, το ?= αναθέτει μόνο αν δεν υπάρχει τιμή και το += προσθέτει.
  10. Οι automatic variables $@, $^, $< και τα wildcard rules (%.o: %.c) αφαιρούν την επανάληψη από ένα Makefile.
  11. Τα built-in rules του Make ήδη ξέρουν να κάνουν compile και link, άρα ένα Makefile για C μπορεί να είναι 2–3 γραμμές.
  12. Το Make χτίζει τον Linux kernel και βρίσκεται κάτω από τα cmake, automake κ.λπ.· δεν περιορίζεται στη C.

Ορολογία

Ελληνικά English Σύντομος ορισμός
μονάδα μετάφρασης translation unit Ένα αρχείο .c μαζί με ό,τι φέρνουν τα #include του
αντικειμενικό αρχείο object file Το .o που βγάζει ο compiler από ένα translation unit
συνδέτης linker (ld) Ενώνει object files και βιβλιοθήκες σε εκτελέσιμο
δυναμική βιβλιοθήκη dynamic library (.so) Βιβλιοθήκη που φορτώνεται στην εκτέλεση, π.χ. libm.so
σφάλμα σύνδεσης linking error Ο linker δεν βρίσκει υλοποίηση συμβόλου (undefined reference)
σύστημα κατασκευής build system Εργαλείο που αυτοματοποιεί το χτίσιμο ενός project
στόχος target Ό,τι μπορεί να παραχθεί, συνήθως ένα αρχείο
προαπαιτούμενο prerequisite Target από το οποίο εξαρτάται ένα άλλο target
συνταγή recipe Οι εντολές shell που φτιάχνουν ένα target
κανόνας rule target: prerequisites μαζί με το recipe
μακροεντολή macro Μεταβλητή του Make με τιμή string, $(NAME)
αυτόματη μεταβλητή automatic variable $@, $^, $<: αλλάζουν τιμή ανά rule
κανόνας μοτίβου wildcard / pattern rule Rule με % που ορίζει οικογένεια rules
ενσωματωμένος κανόνας built-in rule Rule που το Make ξέρει ήδη (π.χ. .o από .c)
παρωχημένο out of date Target που λείπει ή είναι παλαιότερο από prerequisite του

Διάβασμα

Συχνά λάθη

Ερωτήσεις κατανόησης

Ασκήσεις

Ζέσταμα: από τις διαφάνειες (Α26.1–Α26.5)

Σχετικές ασκήσεις από άλλα κεφάλαια

  1. Preprocessor (προεπεξεργασμένο .c), compiler (object file .o), linker (εκτελέσιμο, από τα .o και τις βιβλιοθήκες). [^q2]: Το math.h έχει μόνο τη δήλωση· η υλοποίηση είναι στη libm.so, που πρέπει να δοθεί στον linker με -lm. [^q3]: Ο linker δουλεύει με object files, όχι με τον πηγαίο κώδικα, άρα δεν ξέρει γραμμή ούτε ποια βιβλιοθήκη λείπει. [^q4]: Την επαναμεταγλώττιση ολόκληρου του project: το script ξαναχτίζει τα πάντα σε κάθε εκτέλεση. [^q5]: Target: ό,τι παράγεται· prerequisite: ό,τι χρειάζεται πρώτα· recipe: οι εντολές που το φτιάχνουν· rule: το σύνολο target: prerequisites και recipe. [^q6]: Το target του πρώτου rule του Makefile. [^q7]: primes.c: το primes.o και το main. primes.h: και τα δύο .o και το main. [^q8]: Το = αποτιμά το δεξί μέλος όταν χρησιμοποιηθεί το macro (lazy)· το := τη στιγμή της ανάθεσης (eager). [^q9]: $@ = main, $^ = main.o primes.o, $< = main.o. [^q10]: Wildcard rule: κάθε X.o φτιάχνεται από το X.c με το ίδιο recipe. Το Make έχει ήδη built-in rules για compile και link, οπότε αρκεί να δηλώσετε flags και εξαρτήσεις. ↩

Κατεβάστε το κεφάλαιο: PDF · Markdown · GitHub