Forum: Compiler & IDEs Makefile Grundlagen - Frage


von Erkan (Gast)


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?

von Andreas B. (andreas_b77)


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.

von Erkan (Gast)


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!

von Andreas B. (andreas_b77)


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.

von Erkan (Gast)


Angehängte Dateien:

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?

von Andreas B. (andreas_b77)


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.

von Sven B. (scummos)


Lesenswert?

Ich weise an dieser Stelle mal wieder auf Makefile-Generatoren wie cmake 
hin, die einem vor dem auf Dauer doch recht anstrengenden manuellen 
Warten dieser Makefiles bewahren. In CMake sähe das so aus:
1
project(myproject)
2
add_executable(proc main.c foo.c bar.c)

Grüße,
Sven

von Erkan (Gast)


Angehängte Dateien:

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.

von Zypern-rettender Steuerzahler (Gast)


Lesenswert?

Wie soll denn spule, das in main deklariert ist, gefunden werden? Du 
mußt es ausserhalb, zB in einer Datei deklarieren und diese (ich weiß 
nicht ob gcc bei Vorwärts-Referenzen meckert, vermutl. aber schon) den 
Compiler finden lassen bevor du etwas damit machst.

von Erkan (Gast)


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

von Sven B. (scummos)


Lesenswert?

Der Compiler ist ja ganz zufrieden -- das ist ein Linker-Fehler. Du hast 
die deine statische Variable nur deklariert, es fehlt eine Definition, 
bei der für die Variable dann auch ein Speicherbereich reserviert wird.

von Erkan (Gast)


Lesenswert?

Sven B. schrieb:
> es fehlt eine Definition

Ich definiere es ja in Main.
Wieso funktioniert das nicht?
Alle meine Projekte funktionieren so!!!

von Erkan (Gast)


Lesenswert?

Mit Codeblocks funktioneirts ja genau so...
Wieso nicht mit dem Makefile?
Irgendwas dürfte da bei der Synthax nicht stimmen...

von Andreas B. (andreas_b77)


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.

von Erkan (Gast)


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?

von Sven B. (scummos)


Lesenswert?

Oh, ja, sorry, mein Fehler.

von Andreas B. (andreas_b77)


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.

von Frank M. (ukw) (Moderator) Benutzerseite


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.

von (prx) A. K. (prx)


Lesenswert?

Wenn man GCC verwendet, dann kann man die Abhängigkeiten von 
Header-Files auch automatisch erstellen lassen: 
http://www.ijon.de/comp/tutorials/makefile.html#dep
Erspart Arbeit und man vergisst keine.

von Erkan (Gast)


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

von Erkan (Gast)


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

von Andreas B. (andreas_b77)


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.

von Erkan (Gast)


Lesenswert?

Andreas B. schrieb:
> 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.

DAAAAANKE!!
Funktioniert.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.