Compileren en linken van C++ files

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()

Maak de bovenstaande files hello.h en hello.cpp

Maak de assembly-file hello.s met het commando "g++ -S hello.cpp" en bekijk deze file.

Maak de object-file hello.o met het commando "g++ -c hello.s"

Je kunt de object-file hello.o ook direct uit hello.cpp maken met het commando "g++ -c hello.cpp"

Maak van de object file hello.o een executable met het volgende commando:
"g++ hello.o -L/usr/lib -lstdc++"
Dit produceert een executable met de naam a.out.

Eigenlijk willen we een executable met de naam hello:
"g++ -o hello hello.o -L /usr/lib -lstdc++"

Verwijder de files a.out, hello.o, hello en hoi en type in:
"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.

Verwijder nogmaals de .o files en hello en voer nog een keer "make hello" uit.

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.

Probeer uit: "make", kijk welke files in je directory staan en daarna "make clean" en kijk welke files er nu in je directory staan

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.

Probeer uit: "make", pas de file hello.h aan of alleen zijn timestamp met "touch hello.h". Voer weer "make" uit. Waarom gebeurt er niks ?

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.