In dit overzicht zie je de relaties tussen de verschillende typen files die betrokken zijn bij het maken van een C++ project. Helemaal onderaan staan de header files die met #include in andere header files en implementatiefiles (.cpp) gebruikt worden. De code in de implementatiefiles wordt omgezet in object-files door een compiler, bijvoorbeeld g++.
Hello Goodbye
Een voorbeeld:
/*
* hello.h
*/
class HelloGoodbye
{
public:
HelloGoodbye(void);
void greeting(void);
private:
bool encounter;
};
/*
* hello.cpp
*/
#include <iostream>
#include "hello.h"
using namespace std;
HelloGoodbye::HelloGoodbye(void)
{
encounter=0;
} // HelloGoodbye()
void HelloGoodbye::greeting(void)
{
if(encounter) cout << "Doei" << endl;
else cout << "Hallo" << endl;
encounter = !encounter; // toggle
} // greeting()
main()
{
HelloGoodbye hg;
hg.greeting();
hg.greeting();
hg.greeting();
hg.greeting();
} // main()
"g++ hello.o -L/usr/lib -lstdc++"
Dit produceert een executable met de naam a.out.
"g++ -o hello hello.o -L /usr/lib -lstdc++"
"make hello"
Zoals je ziet heeft make al een aantal ingebouwde regels om uit C++ files een executable te bouwen. Je kunt de hele set van regels bekijken met "make -p".
Zoals je hebt kunnen zien bevat hello.cpp zowel de implementatie van de class HelloGoodbye als de main() routine. Dat is niet netjes en daarom gaan we deze scheiden in een file hello.cpp en hello_main.cpp.
Waarom werkt het nu niet meer ? Vertel waar het mis gaat.
Wat we nu moeten doen is twee .cpp files compileren en daarna deze twee
linken. Bijvoorbeeld als volgt:
g++ -c hello.cpp
g++ -c hello_main.cpp
g++ -o hello hello.o hello_main.o -L /usr/lib -lstdc++
Dit is één keer nog wel leuk, maar als je voor elke wijziging in een source file dit rijtje commando's opnieuw moet uitvoeren gaat de lol er snel af. En dan hebben we het nog maar over twee source files !
We gaan een makefile maken die dit alles in een keer doet. Om te beginnen willen we hetzelfde bereiken als hierboven zodat we dat later kunnen uitbreiden. Het target is de executable hello en we hebben gezien dat deze ontstaat uit twee objects hello.o en hello_main.o die op hun beurt weer ontstaan uit hello.cpp en hello_main.cpp, die hello.h nodig hebben. Hoe schrijven we dat op ?
# Makefile voor HelloGoodbye
#
# Marc_G 2012
hello: hello.cpp hello_main.cpp
g++ -c hello.cpp
g++ -c hello_main.cpp
g++ -o hello hello.o hello_main.o
Je hoeft nu alleen nog maar "make" in te typen en je programma wordt gemaakt. Bovendien wordt gekeken of hello.cpp wel echt gecompileerd moet worden. Als je meteen nog een keer make uitvoert zegt 'ie dat er niks hoeft te gebeuren.
Het kan mooier...
# Makefile voor HelloGoodbye
#
# Marc_G 2012
hello: hello.o hello_main.o
g++ -o hello hello.o hello_main.o
hello.o: hello.cpp
g++ -c hello.cpp
hello_main.o: hello_main.cpp
g++ -c hello_main.cpp
In deze Makefile zie je dat het compileren van hello.cpp en hello_main.cpp is gesplitst. Het voordeel is dat nu niet meer beide files gecompileerd worden als er maar één verandert, zoals in de vorige Makefile het geval was.
Maar het kan nog mooier...
# Makefile voor HelloGoodbye
#
# Marc_G 2012
hello: hello.o hello_main.o
g++ -o hello hello.o hello_main.o
.cpp.o:
g++ -c $<
clean:
/bin/rm -f hello *.o
Je ziet twee regels die we nog niet eerder hadden behandeld en er is wat verdwenen. Eerst de makkelijkste: clean. Het target clean is nergens van afhankelijk maar voert wel een commando uit, namelijk het opruimen van wat resultaatfiles.
De regel die begint met .cpp.o vertelt aan make de manier waarop .o files altijd gemaakt moeten worden uit .cpp files. Je kunt dit ook voor andere typen toepassen: je zou een makefile kunnen maken die .mp3 files uit .aiff files maakt.
Macro's
Je ziet in de bovenstaande Makefile dat bepaalde dingen op diverse plaatsen terugkomen. Dat kan makkelijker door ze bovenin de file eenmalig te definieren en vervolgens met die macro's verder te werken. Kijk eens naar deze Makefile:
# Makefile voor HelloGoodbye
#
# Marc_G 2012
CC = g++
CFLAGS = -Wall
PROG = hello
SRC = hello.cpp hello_main.cpp
OBJS = hello.o hello_main.o
$(PROG): $(OBJS)
$(CC) -o $@ $(OBJS)
.cpp.o:
$(CC) -c $< $(CFLAGS)
clean:
/bin/rm -f $(PROG) $(OBJS)
depend:
makedepend $(SRC)
Er is nog wat bij gekomen: CFLAGS en makedepend. CFLAGS is een
door make herkende macro waarin je allerlei compiler flags kunt zetten,
bijvoorbeeld voor het aangeven welk niveau van warnings je wilt zien,
een optimization level of het opnemen danwel weglaten van debug-info.
Met makedepend kun je automatisch allerlei afhankelijkheidsregels
laten aanmaken. Het programma makedepend loopt door alle opgegeven source
files heen op zoek naar header files en zet alle dependencies in de
Makefile.