Kompilieren

Die Übersichtlichkeit der Seite wird durch Javascript erhöht. Ist dies aktiviert, werden die Texte unter den Überschriften durch Anklicken der Überschriften ein- und ausgeblendet.

g++

g++ [kompilieren einfach]

Um aus einem C++-Quellcode ein lauffähiges Programm zu erzeugen, muss es zum ein kompiliert und zum anderen gelinkt werden. Beides wird durch das Komando:

g++ quellcodeName.cpp

erreicht. In diesem Fall wird aus der angegeben Quellcodedatei ein Programm mit dem Namen a.out erzeugt.

nach oben

Option -Wall [Warnungen]

Mit dem Standardbefehl werden nicht alle Warnungen angezeigt. Da diese allerdings in den meisten Fällen sehr hilfreich sind, sollte die Option -Wall mit angegeben werden. Der Befehl lautet dann.

g++ -Wall quellcodeName.cpp

Gegebenen falls noch mehr Warnungen mit der zusätzlichen Option -Wextra.

g++ -Wall -Wextra prog.cpp

nach oben

Option -o [Benennen]

Um den Namen des Programms abweichend von a.out festzulegen wird die Option -o genutzt:

g++ quellcode.cpp -o Wunschprogrammname

nach oben

Option -c [nur kompilieren]

Hat man mehrere Dateien, möchte mann diese ggf. unabhängig von einander kompilieren und später verlinken. Um g++ anzuweisen, eine Quellcode-Datei nur zu kompilieren und damit eine quellcodename.o Datei zu erstellen gibt es die Option -c

g++ -c quellcode.cpp -o quellcode.o

nach oben

gtkmm

Um grafische programme mit der gtkmm-Bibliothek zu erstellen, benötigt man als ergänzende Option `pkg-config gtkmm-3.0 --libs --cflags`

g++ -Wall `pkg-config gtkmm-3.0 --libs --cflags` -o Programmname

nach oben

gtk

Um grafische Beniutzeroberflächen mit gtk3 zu erstellen wird der Code wie folgt kompiliert:

g++ -Wall `pkg-config gtk+-3.0 --libs --cflags`

nach oben

make

Es ist zu empfehlen in der C++-Programmierung nicht alles in eine Datei zu schreiben. Zum einen ist es möglich auf diese Weise bestimmten Code auch in anderen Programmen auf einfache Weise wiederzuverwenden, zum anderen muss wegen einer kleinen Änderung an einer Stelle nicht das komplette Programm neu kompiliert werden.

Ich will ein Beispiel zeigen, wie ein solches aufgeteiltes Programm aussehen kann und wie es dann mit Hilfe von make auf einfache Weise kompiliert werden kann.

Dateien

Bibliothek mit der Klassendeffinition

Eine Header-Datei, die nichts außer der Definiton einer Klasse, mit Ihren Variablen und den Funktionsprototypen sowie den Konstruktorfunktionen beinhaltet. Diese Datei wird später in der Datei mit der main-Funktion per include Eingefügt, ähnlich dem Einbinden der C++-Standard-Header, wie beispielsweise <iostream>, nur dass statt der spitzen Klammern Anführungszeichen gesetzt werden.

Source-Code

rational.h

#ifndef rational_h
#define rational_h rational_h

class Rational
{
  long zaehler, nenner;

  public:

  Rational();
  Rational(long z, long n);
  Rational(long);

  long getZaehler() const;
  long getNenner() const;

  void add(const Rational& r);
  void sub(const Rational& r);
  void mul(const Rational& r);
  void div(const Rational& r);

  void def(long zaehler, long nenner);
  void eingabe();
  void ausgabe();
  void kehrwert();
  void kuerzen();
};

inline Rational::Rational() : zaehler(1), nenner(1){}
inline Rational::Rational(long z, long n) : zaehler(z), nenner(n){}
inline Rational::Rational(long N) : zaehler(N), nenner(1){}

inline long Rational::getZaehler() const{return zaehler;}
inline long Rational::getNenner() const{return nenner;}

Rational add(const Rational& a,const Rational& b);
Rational sub(const Rational& a,const Rational& b);
Rational mul(const Rational& a,const Rational& b);
Rational div(const Rational& a,const Rational& b);

#endif

nach oben

Datei mit den Funktionsdefinitionen

Hier werden jetzt alle Funktionen, deren Prototypen in der Klasse bekannt gemacht wurden deffiniert. Die Methoden (Klassen-Funktionen) werden durch voranstellen des Klassennamens gefolgt von 2 Doppelpunkten gekennzeichnet.

Diese Datei wird nirgends includiert, includiert allerdings ihrerseits die Datei mit der Klassendeffinition. Um Sie in das Programm einzubinden, wird der Quellcode zunächst mit der Option -c nur kompiliert und die daraus entstehende Objektdatei mit der Endung .o wird später mit der Datei, die die main-Funktion enthält verlinkt. Das hat den Vorteil, dass diese Datei bei Änderungen am Hauptprogramm nicht erneut kompiliert werden muss.

Source-Code

#include "rational.h"
#include <iostream>
#include <cassert>

#define cerr std::cerr
#define cout std::cout
#define cin std::cin
#define endl std::endl

void Rational::add(const Rational& r)
{
  zaehler = zaehler * r.nenner + r.zaehler * nenner;
  nenner = nenner * r.nenner;
  kuerzen();
}

void Rational::sub(const Rational& r)
{
  zaehler = zaehler * r.nenner - r.zaehler * nenner;
  nenner = nenner *r.nenner;
  kuerzen();
}

void Rational::mul(const Rational& r)
{
  zaehler = zaehler * r.zaehler;
  nenner = nenner + r.nenner;
  kuerzen();
}

void Rational::div(const Rational& r)
{
  zaehler = zaehler * r.nenner;
  nenner = nenner * r.zaehler;
  kuerzen(); 
}

void Rational::def(long z, long n)
{
  zaehler = z;
  nenner = n;
  assert(nenner != 0);
  kuerzen();
} 

void Rational::eingabe()
{
  cerr << "Zähler: ";
  cin >> zaehler;
  cerr << "Nenner: ";
  cin >> nenner;
  assert(nenner !=0);
  kuerzen();
}

void Rational::ausgabe()
{
  cout << zaehler << '/' << nenner;
}

void Rational::kehrwert()
{
  long temp = zaehler;
  zaehler = nenner;
  nenner = temp;
  assert(nenner != 0);
}

long ggt(long a, long b)
{
  long rest;
  while (b >0)
  {
    rest = a % b;
    a = b;
    b = rest;
  }
  return a;
}

void Rational::kuerzen()
{
  // Vorzeichen merken und Betrag bilden
  int vorzeichen = 1;
  if (zaehler < 0) 
  {
    vorzeichen *= -1;
    zaehler *=-1;
  }
  if (nenner < 0)
  {
    vorzeichen *= -1;
    nenner *=-1;
  }

  long teiler = ggt(zaehler,nenner);

  // Vorzeichen wiederherstellen und kuerzen
  zaehler /=teiler*vorzeichen;
  nenner /=teiler;
  }

Rational add(const Rational& a,const Rational& b)
{
  Rational r = a;
  r.add(b);
  return r;
}

Rational sub(const Rational& a,const Rational& b)
{
  Rational r = a;
  r.sub(b);
  return r;
}

Rational mul(const Rational& a,const Rational& b)
{
  Rational r = a;
  r.mul(b);
  return r;
}

Rational div(const Rational& a,const Rational& b)
{
  Rational r = a;
  r.div(b);
  return r;
}

nach oben

Datei mit der main-Funktion

Die Datei mit der main-Funktion stellt das Hauptprogramm dar. Mit der Main-Funktion beginnt jedes C++-Programm. In dieser Datei wird die Header-Datei mit der Klassendeffinition per #include "name.h" eingebunden. Die Datei mit der Funktionsdeffinition wird entweder direkt mit der main-Datei an den Compiler übergeben und direkt kompiliert und verlinkt. Dazu würde folgender Befehl genügen:

g++ main.cpp Funktionsdeffinitionsdatei.cpp -o Programmname

Dies hätte den Nachteil, dass bei jeder Änderung alle Programmteile erneut kompiliert werden müssten. Der bessere Weg ist, zunächst für beides eine Objektdatei zu erstellen. So hat mann von beiden Dateien das Kompilat und verlinkt diese Dateien nur noch durch Übergabe an den Compiler:

g++ -c -Wall bruchrechnung.cxx rational.cpp
g++ *.o -o bruchrechnung

Wenn sich im Nachgang an der Datei bruchrechnung.cxx etwas ändert, muss nur noch diese neu kompiliert werden und kann dann direkt neu verlinkt werden:

g++ -c -Wall bruchrechnung.cxx
g++ *.o -o bruchrechnung

Beispiel Source-Code bruchrechnung.cxx mit der main-Funktion


#include "rational.h"
#include <iostream>

#define cout std::cout
#define cin std::cin
#define endl std::endl

void rechne(Rational& a, Rational& b);
char op();

int main(int argc, char* argv[] )
{
  Rational a, b, Ergebnis;
  char fortsetzen='y';
  while (fortsetzen !='n')
  {
    a.eingabe();
    b.eingabe();
    rechne(a,b);
    cout << "Noch einmal (j/n)?" << endl;
    cin >> fortsetzen;
  }
}

char op()
{
  // Fragt die gewünschte Rechenoperation ab
  // gibt das Operatorzeichen der Rechenoperation zurück

  char a;
  bool eingabeOK= false;
  while (not eingabeOK)
  {
    cout << " Was soll gerechnet werden (+,-,*,/) ? " << endl;
    cin >> a;
    switch(a)
    {
      case '+':
      case '-':
      case '*':
      case '/':
        eingabeOK = true;
        break;
      default:
        cout << "Bitte nur einen gültigen Rechenoperator auswählen (+,-,* oder /)"<< endl;
    }
  }
  return a;
}

void rechne(Rational& a, Rational& b)
{
  Rational Ergebnis;
  switch(op())
  {
    case '+':
      Ergebnis = add(a,b);
      a.ausgabe();
      cout <<  " + "; 
      b.ausgabe(); 
      cout << " = ";
      Ergebnis.ausgabe();
      cout << endl;
      break;
    case '-':
      Ergebnis = sub(a,b);
      a.ausgabe();
      cout <<  " - "; 
      b.ausgabe(); 
      cout << " = ";
      Ergebnis.ausgabe();
      cout << endl;
      break;
    case '*':
      Ergebnis = mul(a,b);
      a.ausgabe();
      cout <<  " * "; 
      b.ausgabe(); 
      cout << " = ";
      Ergebnis.ausgabe();
      cout << endl;
      break;
    case '/':
      Ergebnis = div(a,b);
      a.ausgabe();
      cout <<  " / "; 
      b.ausgabe(); 
      cout << " = ";
      Ergebnis.ausgabe();
      cout << endl;
  }
}

nach oben

makefile-Datei

Auf Dauer und jeh mehr Dateien im Bunde sind wird das manuelle kompilieren über die händische Eingabe der Befehle zu aufwendig. Mit dem Programm make läst sich diese Aufgabe automatisieren. Zusätzlich und worauf ich zunächst nicht eingehen möchte ist es möglich mit Hilfe von Variablen flexibel Flags zu setzten, die Einfluss auf den Kompiliervorgang haben. Das Programm sucht nach einer Datei namens makefile und kompiliert und linkt alle geänderten oder neuen Dateien entsprechend den in dieser Datei festgelegten Regeln.

Dabei wird immer zunächst das Ziel gefolgt von einem Doppelpunkt und da hinter dann die Dateien, die für die Erstellung der Zieldatei benötigt werden. In der Zeile darunter wird mit einem Tab eingerückt der Kompiler-Befehl angegeben. Darunter folgt dann nach dem selben Aufbau die nächste Regel zur Erstellung ggf. zuvor benötigter Dateien. Um also ein Programm zu verlinken, wird zunächst der gewünschte Programmname gefolgt von einem Doppelpunkt und den Objektdateinamen (.o) angegeben. In der Zeile darunter eingerückt mit einem Tab der dafür benötigte Kompilerbefehl. Darunter folgt nach dem gleichen Muster die Regel für die Erstellung einer benötigten Objektdatei, also dem Compilat aus dem Quelltext. Und darunter die weiteren Regeln für die Erstellung eventueller weiterer Objektdateien. Dies ist beliebig erweiterbar. Sind alle Dateien in einem Verzeichnis, genügt ein einfaches make, um alle notwendigen Schritte auszuführen. Ein erneuter Aufruf wird nur geänderte Dateien neu compilieren und die Dateien neu verlinken. Das makefile für das Beispiel sieht wie folgt aus:

Source-Code makefile

bruchrechnung: bruchrechnung.o rational.o
  g++ bruchrechnung.o rational.o -Wall -g -o bruchrechnung
bruchrechnung.o: bruchrechnung.cxx
  g++ -c -Wall -g bruchrechnung.cxx -o bruchrechnung.o
rational.o: rational.cpp
  g++ -c -Wall -g rational.cpp -o rational.o

nach oben