Makefile Grundlagen - Frage

Gast #3089990
Lesenswert?

1
prog: main.o foo.o bar.o
2
  gcc -o prog main.o foo.o bar.o
3

4
main.o: main.c
5
  gcc -c main.c
6

7
foo.o: foo.c
8
  gcc -c foo.c
9
  
10
bar.o: bar.c
11
  gcc -c bar.c

Bei diesem Beispiel verstehe ich nicht ganz wozu ich die erste Zeile bei 
allen Anweisungen brauche? Dient das zur übersicht?

Kann man außerdem das:
gcc -o programm main.c foo.c bar.c mit
gcc -o programm main.o foo.o bar.o gleichsetzen ??

Beides erzeugt die selbe ausführbare Datei wenn ich das richtig sehe.
Wann verwendet man jetzt was?
#3090047
Lesenswert?

Die erste Zeile? Damit beginnt eine Regel. Alle Anweisungen gehören zu 
einer Regel, sonst hätten sie gar keine Daseinsberechtigung. Hinter dem 
Namen des Ziels und dem Doppelpunkt stehen die Abhängigkeiten. Make 
überprüft für jedes Ziel (falls es schon existiert) ob eines ihrer 
Abhängigkeiten neuer ist und übersetzt es in dem Fall neu. Das ist der 
Hauptzweck von Make, sonst könnte man auch einfach ein Shell-Skript 
verwenden.

Erkan schrieb:
> Kann man außerdem das:
> gcc -o programm main.c foo.c bar.c mit
> gcc -o programm main.o foo.o bar.o gleichsetzen ??

Allgemein ja, denn gcc enthält selber ein paar Regeln, um aus Dateinamen 
die passenden Aktionen zu ermitteln und auszuführen (in diesem Fall mit 
dem C-Compiler übersetzen und dann linken).

Im konkreten Fall nein, denn die Abhängigkeit ist von den Objektdateien. 
Würde man das so machen wollen, kann man die Einträge für die 
Objektdateien alle entfernen. Allerdings werden dann bei jeder Änderung 
alle Sourcen neu übersetzt.
Gast #3090065
Lesenswert?

Andreas B. schrieb:
> Make
> überprüft für jedes Ziel (falls es schon existiert) ob eines ihrer
> Abhängigkeiten neuer ist und übersetzt es in dem Fall neu.

Ok, daher auch das hier:
gcc -o programm main.o foo.o bar.o

sonst wie gesagt ein shell script mit:
gcc -o programm main.c foo.c bar.c


Weil ich könnte ja genau so das hier in mein makefile schreiben:

1
prog: main.c foo.c bar.
2
  gcc -o prog main.c foo.c bar.c

Viel weniger Code, übersichtlicher und kompakter.

Andreas B. schrieb:
> Make
> überprüft für jedes Ziel (falls es schon existiert) ob eines ihrer
> Abhängigkeiten neuer ist und übersetzt es in dem Fall neu.
Woher weiß make aber, dass ich eine Datei verändert habe?
Geht das in etwa so vor sich: Wenn eine datei sich verändert hat, dann 
wird eben der Abschnitt
1
foo.o: foo.c
2
  gcc -c foo.c
vom makefile aufgerufen?
Dann würde es auch sinn machen.
Danke für die Erklärung!
#3090091
Lesenswert?

Erkan schrieb:
> Viel weniger Code, übersichtlicher und kompakter.

Und muss bei jeder Änderung das komplette Projekt neu übersetzen statt 
nur die geänderte Datei. Außerdem sind Header-Dateien nicht als 
Abhängigkeit erfasst, also wird es unter Umständen nicht neu übersetzt 
obwohl nötig.


Erkan schrieb:
> Woher weiß make aber, dass ich eine Datei verändert habe?

Es vergleicht die Zeitstempel der letzten Änderungen laut Dateisystem.


Das zitierte Makefile ist sowieso etwas suboptimal, zumindest wenn es um 
GNU make geht. Da verwendet man besser Variablen um nicht alles doppelt 
hinzuschreiben und dabei etwas zu vergessen.
1
prog: main.o foo.o bar.o
2
  $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
3

4
main.o: main.c
5
foo.o: foo.c
6
bar.o: bar.c

Das verwendet Variablen, die übersichtlich am Anfang des Makefiles 
gesetzt werden können. Bei den Regeln kann man meist das Kommando 
weglassen, weil GNU make eine ausführliche Liste impliziter Regeln 
enthält (für .c nach .o etwa eine Regel der Form "$(CC) -c $(CPPFLAGS) 
$(CFLAGS)" plus den entsprechenden Dateinamen).

Streng genommen kann man hier auch diese Regeln weglassen, da make 
automatisch nach passenden möglichen Sourcen sucht wenn es ein .o bauen 
muss und diese Regel implizit anwendet. Aber zu den Abhängigkeiten 
gehören in der Praxis auch Header-Dateien, und die müssen explizit 
angegeben werden.
Gast #3090107
Lesenswert?

Habs jetzt wie im Bild.
Eine Frage noch, wenn ich Header einbinden will, schreib ich dann 
einfach zu den OBJECTS header.o dazu?
Und wie lasse ich es neu compilieren wenn du sagst, dass es nicht 
automatisch compiliert wird?

Heißt das, beim header muss dann stehn:

programm: $(OBJECTS)
      $(Compiler) -o $@ $(OBJECTS)
      $(Compiler) -c $@ header.h

?

überschreibt da die 3. zeile nicht die erste?
Angehängte Dateien:
#3090368
Lesenswert?

Wenn du die Variable CC statt Compiler nennst kannst du dir deine eigene 
Pattern Rule fürs Übersetzen von .c nach .o sparen, denn die ist wie 
gesagt schon vordefiniert. Außerdem fehlt deinem Pattern beim 
Compileraufruf ein "-o $@", bei einer Regel wie "foo.o: bar.c" würde es 
sonst schiefgehen (es würde bar.o statt foo.o erzeugen).

Erkan schrieb:
> Eine Frage noch, wenn ich Header einbinden will, schreib ich dann
> einfach zu den OBJECTS header.o dazu?

Nein, zu den Abhängigkeiten der Dateien, die davon abhängen (sie per 
#include einbinden). Etwa
1
main.o: main.c header.h

Erkan schrieb:
> Und wie lasse ich es neu compilieren wenn du sagst, dass es nicht
> automatisch compiliert wird?

Es wird automatisch übersetzt, /wenn es nötig ist./ Wenn du ein 
Neuübersetzen erzwingen willst, ist es am einfachsten die erzeugten 
Dateien zu löschen. Dann müssen sie logischerweise neu übersetzt werden.

Erkan schrieb:
> überschreibt da die 3. zeile nicht die erste?

Da überschreibt nix. Die erste Zeile benennt das Ziel und die 
Abhängigkeiten. Die folgenden Zeilen werden alle ausgeführt, wenn das 
Ziel neu erzeugt werden muss. Was drin steht interessiert make nicht, es 
ersetzt einfach die Variablen in jeder Zeile durch ihre aktuellen Werte 
und übergibt das Ergebnis einer Shell zur Ausführung.
Gast #3090683
Lesenswert?

Andreas, danke für deine äußerst gut Hilfe!
Ich werde einpaar von dir vorgeschlagene Dinge nicht anwenden, da ich 
das ganze übersichtlich halten will, ich scheitere aber immer noch daran 
das Headerfile erfolgreich einzubinden.

Habe es so versucht:
1
CC = gcc
2
OBJECTS = main1.o ausgabe.o 
3

4
# $@ entspricht dem vollen Namen des Ziels einer Regel
5
# % ... bedeutet suche nach Datei 
6

7
programm: $(OBJECTS)  
8
  $(CC) -o $@ $(OBJECTS)
9

10
main1.o:
11
  $(CC) -c main1.c 
12
  $(CC) -c header1.h
13

14
ausgabe.o:
15
  $(CC) -c ausgabe.c
16
  $(CC) -c header1.h

Jedoch funktioniert es nicht ganz.
Kriege als Fehlermeldung:
1
gcc -o programm main1.o ausgabe.o 
2
ausgabe.o: In function `ausgabe':
3
ausgabe.c:(.text+0x20): undefined reference to `spule'
4
collect2: ld returned 1 exit status
5
make: *** [programm] Error 1

Ich verstehe aber nicht ganz wieso. Ich habe das Headerfile in allen C 
Files eingebungen. Und die Variable spule ist schon in main1.c 
initialisiert.

Ich weiß immer noch nicht ganz wo ich das Headerfile dazugeben soll, 
einige machen es doch in der Vsriable OBJECTS.
Aber du sagst schreib es in main1.o...
Ich bin da gerade so orientierungslos, weil ich weiß nicht wieso und was 
für einen Unterschied es macht.
Angehängte Dateien:
Gast #3090783
Lesenswert?

Zypern-rettender Steuerzahler schrieb:
> Wie soll denn spule, das in main deklariert ist, gefunden werden?

Durch das Headerfile. Es müsste doch dem ausgabe.c File dadurch bekannt 
sein.
Ich schreibe meine Programme bisher immer so, nur halt in 
Entwicklungsumgebungen, dort hat es mit 1000en von Codezeilen immer gut 
funktioniert die Variable nur in einem File zu initialisieren
#3090815
Lesenswert?

Erkan schrieb:
> main1.o:
>   $(CC) -c main1.c
>   $(CC) -c header1.h

Nein, so geht das nicht. Damit rufst du den Compiler zwei mal auf, 
einmal für die main1.c, einmal versuchst du header1.h für sich allein zu 
übersetzen. Beim Aufruf des Compilers wirst du header1.h nie erwähnen 
müssen, die wird durch #include schon eingebunden. Und es fehlen jetzt 
die Abhängigkeiten.

Also nochmal:
1
main1.o: main1.c header1.h
2
        ${CC} ${CFLAGS} -c main1.c

(Und natürlich entsprechend für ausgabe.o) Das ist alles für das 
einfaches Beispiel, mehr gehört da nicht hin. CFLAGS habe ich noch 
eingefügt, das braucht man fast immer (etwa um Debug- und 
Optimierungs-Flags einzuschalten).

Aber da in GNU make eine passende implizite Regel schon existiert, 
reicht auch:
1
main1.o: main1.c header1.h
Das Kommando zum Übersetzen müsste man wirklich nur schreiben, wenn die 
vorgegebene implizite Regel überhaupt nicht passt. Also eigentlich nur 
bei einem Compiler, der ganz anders als gcc aufgerufen wird.
Gast #3090837
Lesenswert?

So danke!! Jetzt funktioniert es, habs so:
1
    main1.o: main1.c header1.h
2
     gcc -c main1.c header1.h

Die Flags lasse ich mal raus.
Ich hatte lediglich vergessen in die erste Zeile zu schreiben, dass 
beide files das neue machen sollen.

Danke Andreas für die Hilfe!!!

PS: Gibt es eigentlich sonst noch irgendwelche Regeln außer diese?
Kann man auch Shellbefehle in makefiles einbauen?
#3091359
Lesenswert?

Erkan schrieb:
> PS: Gibt es eigentlich sonst noch irgendwelche Regeln außer diese?
> Kann man auch Shellbefehle in makefiles einbauen?

Natürlich kann man, die Kommandos in den Regeln werden einfach einer 
Shell zur Ausführung übergeben, wie ich schon geschrieben habe. Wenn du 
mit andere Regeln meinst, etwas anderes als nur Sachen zu übersetzen, 
auch das:
1
clean:
2
    $(RM) programm $(OBJECTS)
3

4
.PHONY: clean

Das .PHONY ist eine Pseudo-Regel, die make dahingehend auswertet, dass 
alle Abhängigkeiten nicht wirklich eine Ergebnisdatei erzeugen. Hier 
also, dass bei "make clean" nicht geschaut wird, ob eine Datei clean 
bereits existiert sondern die Regel bedingungslos ausgeführt wird.
Moderator Persönliche Seite #3091368
Lesenswert?

Erkan schrieb:
> So danke!! Jetzt funktioniert es, habs so:
>
1
>     main1.o: main1.c header1.h
2
>      gcc -c main1.c header1.h
3
>

Ist trotzdem falsch. Statt
1
  main1.o: main1.c header1.h
2
     gcc -c main1.c header1.h


muss es heißen:
1
  main1.o: main1.c header1.h
2
     gcc -c main1.c

Man wirft dem Compiler keine Header-Dateien (*.h) zum Fraß vor, sondern 
nur die .c-Dateien. In der Abhängigkeits-Regel müssen natürlich alle 
benutzten Header-Dateien angegeben werden.

Von daher ist:
1
  main1.o: main1.c header1.h

richtig, hingegen
1
     gcc -c main1.c header1.h

falsch.
Gast #3091997
Lesenswert?

Andreas B. schrieb:
> clean:
>     $(RM) programm $(OBJECTS)
>
> .PHONY: clean

Cool, was genau passiert da eigentlich?

clean ist nehme ich mal an eine Regel.
Aber was ist die Variable RM ?

Gibt es irgendwo tutorials die diese ganzen Befehle detailliert 
behandeln? Ich finde kaum was im Internet, wo auch die ganzen Befehle 
sehr übersichtlich und verständlich erklärt werden?

Das kann doch nicht sein
Gast #3092078
Lesenswert?

Ok, bin jetzt auf einiges draufgekommen.
Shell Befehlele kann man anscheinend direkt nach der Regel definieren 
und die Regel kann über make REGELNAME aufgerufen werden.
Das ist einfach klasse!!

Hab nur ne Frage wie ich bei nicht vorhanden einer Datei folgende 
Fehlermeldung verhindere:
1
rm *.o
2
rm: cannot remove `*.o': No such file or directory
3
make: *** [clean] Error 1

Mein Makefile:
1
CC = gcc
2
OBJECTS = main1.o ausgabe.o
3

4
programm: $(OBJECTS)
5
  $(CC) -o $@ $(OBJECTS)
6

7
main.o: main.c header1.h
8
  $(CC) -c main.c
9

10
ausgabe.o: ausgabe.c header1.h
11
  $(CC) -c ausgabe.c
12

13
clean:
14
  rm *.o
15
  clear
16
  ls
#3092272
Lesenswert?

Erkan schrieb:
> Ok, bin jetzt auf einiges draufgekommen.
> Shell Befehlele kann man anscheinend direkt nach der Regel definieren
> und die Regel kann über make REGELNAME aufgerufen werden.
> Das ist einfach klasse!!

Irgendwie habe ich das Gefühl, dass du einfach nur meine Beispiele 
abtippst und meine Erklärungen nicht liest. Eben das habe ich auch schon 
zwei mal erklärt.

So, noch was: "make ziel1 ziel2 ziel3 ..." ist an Sich der Normalfall, 
damit wird ziel1, ziel2, usw. aktualisiert. Das kannst du für beliebige 
Regeln machen (also auch "make main.o"). Wenn du keine Ziele angibst, 
nimmt make einfach die erste Regel im Makefile. Sinnvollerweise lässt 
man also die Regel, die das ganze Projekt baut, die erste im Makefile 
sein.

Erkan schrieb:
> rm *.o
> rm: cannot remove `*.o': No such file or directory
> make: *** [clean] Error 1

Ist zu erwarten, wenn die Dateien nicht existieren. $(RM) ist mit "rm 
-f" vordefiniert, wenn du das nicht verwendest willst musst du selbst 
auch -f verwenden.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren