Einführung in die Programmiersprache C

Aus EasyLinux 03/2013

Einführung in die Programmiersprache C

C verstehen

Die meisten Anwendungen, die für Linux verfügbar sind, haben die Entwickler in C oder C++ programmiert. Das spielt für die Installation eines fertigen Pakets keine Rolle, doch in seltenen Fällen müssen Sie in den Quelltext schauen.

Haben Sie ein Programm im Internet entdeckt, das nur als Quelltextarchiv verfügbar ist? Solche Archive erkennen Sie meist an einer der Endungen .tar.gz, .tgz oder .tar.bz2. Eine Archivdatei herunterzuladen und auszupacken, ist noch der leichteste Schritt: Die Dateimanager von KDE und Gnome bieten per Rechtsklick auf die Archivdatei entsprechende Optionen, und auch auf der Kommandozeile geht es schnell mit einem der folgenden drei Befehle (abhängig von der Endung):

tar xzf archivdatei.tgz
tar xzf archivdatei.tar.gz
tar xjf archivdatei.tar.bz2

Sicherheitshalber sollten Sie diese Kommandos in einem neu angelegten Verzeichnis ausführen, in das Sie vorher nur das Archiv kopiert haben – zwar entsteht beim Entpacken meist ein neuer Ordner, der einen ähnlichen Namen wie die Archivdatei trägt, in seltenen Fällen landen aber auch alle im Archiv gesicherten Dateien im aktuellen Verzeichnis (und “müllen” es damit zu).

Programm übersetzen

Mit den Quelldateien kann Linux zunächst nichts anfangen, denn sie sind nicht ausführbar. Sie müssen erst mit einem C-Compiler und weiteren Hilfsprogrammen ein ausführbares Programm erstellen (kompilieren) und dann installieren. Das klappt nur, wenn auf dem Rechner die Entwicklungsumgebung installiert ist. Geben Sie probeweise gcc --version ein: Wenn der C-Compiler installiert ist, erhalten Sie ein paar Zeilen mit Versionsinformationen, anderenfalls eine Fehlermeldung.

Fehlt der Compiler, holen Sie die Installation der Entwicklertools nach. Unter Ubuntu/Kubuntu erledigen Sie das mit folgendem Befehl:

sudo apt-get install build-essential

Unter OpenSuse erreichen Sie das Ziel mit folgendem Kommando:

sudo zypper in -t devel_C_C++

In beiden Fällen landet eine ganze Reihe von neuen Paketen auf Ihrem Rechner, die Tools laden diese aus den Repositories der Distributionen herunter.

Dreischritt

Meist folgt nach dem Entpacken der Quelltexte der so genannte klassische Installationsdreischritt configure; make; make install:

./configure
make
sudo make install

Alle drei Befehle führen dazu, dass Ihr Bildschirm sich mit Unmengen an Systemmeldungen füllt. Was passiert hier alles?

Der erste Schritt ./configure (der unbedingt mit einem Punkt und Schrägstrich vor dem Wort “configure” eingegeben werden muss) startet das im aktuellen Verzeichnis liegende Shell-Skript namens configure: Dieses hat der Entwickler für Sie erstellt, es hat die Aufgabe, sich auf Ihrem Linux-System genau umzusehen. Es prüft, welches Betriebssystem und welche Version Sie verwenden (das gleiche Quelltextarchiv lässt sich in der Regel auch auf anderen Unix-Varianten verwenden), welcher Compiler installiert ist (unter Linux meistens der GNU C Compiler gcc) und ob alle benötigten Bibliotheken in ausreichend aktuellen Versionen vorliegen. Ist alles zur vollen Zufriedenheit von configure, dann erzeugt das Skript ein Makefile: Das ist ein “Rezept”, welches festlegt, in welcher Reihenfolge die Quelldateien mit bestimmten Tools in fertige, ausführbare Programme übersetzt werden. Da die meisten Programme mit verschiedenen Linux-Versionen und weiteren Betriebssystemen wie FreeBSD oder teilweise sogar Windows kompatibel sind, gibt es kein einheitliches Makefile – configure untersucht den Rechner und erstellt es passend für Ihren PC.

Das Makefile brauchen Sie für die nächsten beiden Schritte. Wenn Sie das Dienstprogramm make aufrufen, arbeitet es Ihr frisch erstelltes Makefile der Reihe nach ab: Das Makefile enthält eine rezeptähnliche Auflistung, was alles in welcher Reihenfolge geschehen muss, um ein fertiges (ausführbares) Programm zu erzeugen. Die beiden Schritte ./configure und make können je nach Umfang des Programmes viel Zeit benötigen.

Schließlich kopiert sudo make install alle erstellten Dateien an die vorgesehenen Stellen in Ihrem Dateisystem: Programme selbst landen meist unter /usr/bin oder /usr/local/bin, Hilfeseiten (man pages) unter /usr/share/man oder /usr/local/share/man, Konfigurationsdateien in /etc usw. Für diese Kopieraktionen sind Root-Rechte nötig, darum steht vor dem Kommando make install noch sudo.

Damit ist die Installation abgeschlossen: Wenn das erstellte Programm funktioniert, können Sie das Verzeichnis, in dem Sie die Übersetzung durchgeführt haben, wieder löschen.

Wenn etwas schief geht

Manchmal gelingt die Programmübersetzung nicht, zum Beispiel bei sehr alten Quelltexten, die zu modernen Linux-Versionen nicht mehr passen. Doch fortgeschrittene Anwender mit Programmierkenntnissen können solche Probleme oft beheben. Darum geben wir nun eine sehr oberflächliche Einführung in die Programmiersprache C, die Ihnen helfen kann, solche Programme dennoch zu übersetzen, wenn Sie den erhöhten Aufwand nicht scheuen.

Komplexe Programme verwenden mehrere Quellcode-Dateien, meist mit der Dateiendung .c. Der Compiler übersetzt dann jede dieser Dateien einzeln in eine so genannte Objektdatei mit Endung .o. Solche Dateien sind noch nicht ausführbar, obwohl sie bereits Anweisungen in Maschinensprache für Ihren Rechner enthalten. Wenn alle Übersetzungsschritte abgeschlossen sind, läuft noch der “Linker”, der die Objektdateien zu einem ausführbaren Programm (ohne Dateiendung) oder zu einer Bibliothek (meist mit der Dateiendung .so, die für “shared object”, also “gemeinsam verwendetes Objekt” steht) zusammenbindet.

Daneben liegen in den Quelltextordnern meist noch mehrere Header-Dateien mit Dateiendung .h, die keinen Quellcode, sondern Informationen zu den im Quellcode zu findenden Funktionen enthalten – mehr dazu im Kasten Header-Dateien.

Header-Dateien

Während die Bibliotheken von fertigen Programmen verwendet werden, muss dem Compiler beim Übersetzen eines Programmes gesagt werden, wie auf die Bibliotheken zugegriffen werden kann, d. h. welche Programmfunktionen sie bereitstellen. Dazu dienen die Header- oder Include-Dateien, die die Endung .h haben und von den C- oder C++-Programmen eingebunden werden. Die Include-Dateien finden Sie meist in /usr/include, /usr/local/include und deren Unterverzeichnissen.

Dass ein Programm Header-Dateien verwendet, erkennen Sie an Zeilen der Form

#include <headerdatei.h>

die immer am Anfang einer Quellcodedatei auftauchen. Gelegentlich sehen Sie auch die Variante

#include "headerdatei.h"

(mit Anführungszeichen statt Kleiner- und Größerzeichen) – dann handelt es sich um Headerdateien, die das Programm selbst mit bringt, und der C-Compiler sucht diese nicht in den Standard-Include-Verzeichnissen, sondern erwartet sie im selben Ordner, der auch die C-Programmdatei enthält.

Hello World

Für jeden Artikel, der sich mit einer Programmiersprache beschäftigt, scheint der Abdruck eines minimalen “Hello World”-Programms zwingend zu sein; wir wollen mit dieser Konvention nicht brechen und präsentieren hello-world.c:

#include <stdio.h>
int main () {
  printf ("Hallo Welt\n");
  return 0;
}

Die erste Zeile dieses kurzen Programms können Sie zunächst ignorieren, wir kommen gleich darauf zurück. In der zweiten bis letzten Zeile definiert das Programm eine Funktion main. So heißt immer die Hauptfunktion eines C-Programms, die automatisch startet, wenn Sie das fertige Programm ausführen. Sie können in einem Programm beliebig viele Funktionen schreiben, aber eine davon muss main heißen, ansonsten weiß der Compiler nicht, wo es später losgehen soll.

Bei Funktionen in C müssen Sie immer angeben, welche Argumente die Funktion akzeptiert und welchen Typ der Rückgabewert hat. Im obigen Beispiel bedeutet int main, dass diese Funktion einen Integer-Wert als Rückgabewert hat, und die leeren Klammern () hinter main sagen aus, dass die Funktion keine Argumente erwartet.

Ein Beispiel für eine Funktion mit Argumenten ist die folgende:

int summe (int a, int b) {
  return a + b;
}

Auch diese Funktion gibt einen Integer-Wert zurück (int summe), aber anders als main erwartet sie zwei Argumente a und b beim Aufruf, die ebenfalls Integers sind (int a, int b).

Die geschweiften Klammern { und } markieren den Anfang und das Ende eines Blocks – in beiden bisher gezeigten Funktionen legen sie fest, welche Befehle Teil der Funktion sind.

Doch zurück zum Hello-World-Programm: In der main-Funktion stehen nur zwei Befehle. Der erste ist printf ("Hallo Welt\n"); und ruft die Bibliotheksfunktion printf auf. Die ist dafür zuständig, Texte auf der Konsole auszugeben. Das Argument steht in Klammern und ist in diesem Fall ein String, also eine Zeichenkette. Der String "Hallo Welt\n" enthält am Ende mit \n ein Sonderzeichen: \n steht für “newline”, also einen Zeilenumbruch. Es gibt noch weitere solche Sonderzeichen, z. B. \t für ein Tabulatorzeichen. Wollen Sie einen Backslash ausgeben, müssen Sie diesem einen weiteren voranstellen (\\).

Am Ende jedes Befehls steht ein Semikolon: Damit sagen Sie dem Compiler, dass der aktuelle Befehl abgeschlossen ist. Sie können beim Programmieren längere Befehle über mehrere Zeilen verteilen, also z. B. in main auch alternativ

printf
  ("Hallo Welt\n");
return
  0;

schreiben. Das ist bei diesem Beispiel nicht sinnvoll, bei längeren Kommandos hingegen schon. Das zweite Kommando return 0; verlässt die Funktion und legt dabei auch gleich den Rückgabewert fest. Da main das Hauptprogramm ist, bedeutet der return-Aufruf, dass hier das ganze Programm endet. Der Rückgabewert 0 signalisiert bei Linux-Programmen, dass das Programm korrekt gearbeitet hat; Werte ungleich 0 stehen für einen Programmabbruch mit Fehlern. Sie können den Rückgabewert in der Shell mit

echo $?

abfragen. In “echten” Funktionen, die dazu dienen, etwas zu berechnen, gibt es keinen Standardrückgabewert, stattdessen geben diese mit return das Ergebnis der Berechnungen zurück, wie z. B. im Kommando return a + b; in der Funktion summe.

Vielleicht haben Sie sich beim Beispielprogramm gefragt, woher der Compiler die Funktion printf kennt – diese ist nicht fest in die Programmiersprache C eingebaut, sondern wird über eine Standard-Eingabe-/Ausgabe-Bibliothek zur Verfügung gestellt. Damit der Compiler weiß, dass es die Funktion gibt (und welche Parameter sie erwartet), bindet das Programm mit

#include <stdio.h>

eine Header-Datei ein. In der Datei stdio.h findet sich u. a. die Beschreibung von printf, und beim Kompilieren fügt der Compiler automatisch die Standardbibliothek libc.so.6 zum Programm hinzu (siehe Kasten Header-Dateien).

Kommentare

C kennt eigentlich nur eine Möglichkeit, Code zu kommentieren: Ein Kommentar kann an fast jeder Stelle auftauchen, und er beginnt mit /* und endet mit */. Zwei Beispiele dafür sind

printf ("Hallo\n");   /* Hallo */
printf (/* Hallo */ "Hallo\n");

Die erste Variante ist normal, dort folgt der Kommentar nach einem Befehl. Bei der zweiten Version steht der Kommentar mitten im printf-Aufruf – so etwas ist selten zu finden, aber erlaubt.

Neben diesen klassischen C-Kommentaren, bei denen man Anfang und Ende mit /* ... */ kennzeichnet, ist es beim unter Linux verwendeten GNU-C-Compiler auch möglich, Kommentare in der Syntax zu verwenden, die erst mit der C-Nachfolgersprache C++ eingeführt wurde:

printf ("Hallo\n");   // Hallo

Mit // leitet man einen Kommentar ein, und er läuft bis zum Ende der Zeile (muss also nicht explizit beendet werden).

Fallunterscheidung

Für die If-Then-Else-Konstruktion, mit der Sie – abhängig vom Ergebnis eines Tests – unterschiedlichen Code ausführen können, verwendet C auch das Schlüsselwort if. Es gibt kein then, aber ein else, und ein Stück Beispielcode sieht wie folgt aus:

if (x<10) {
  printf ("x < 10\n");
} else {
  printf ("x >= 10\n");
}

Den else-Zweig muss es nicht geben, und die geschweiften Klammern sind nur zwingend, wenn in einem Fall mehrere Befehle ausgeführt werden sollen. Zudem setzen Programmierer manchmal den Code direkt hinter das if – eine Variante des obigen Blocks (ohne den else-Fall) ist also

if (x<10) printf ("x < 10\n");

Es gibt zudem noch eine hässliche aber platzsparende Abkürzung der if-Konstruktion: Der Befehl

printf ((x<10) ? "x < 10\n" : "x >= 10\n");

ist die kompakte Variante des kompletten if-…-else-Blocks von oben; die allgemeine Syntax ist (bedingung ? ausdruck1 : ausdruck2), und Programmierer setzen sie oft in Zuweisungen ein, z. B. wie folgt:

x_ist_10 = (x==10 ? 1 : 0);

Aus Gründen der Lesbarkeit sollten Sie solchen Code vermeiden, er kann Ihnen aber in C-Programmen begegnen – darum erwähnen wir ihn. Der Test auf Gleichheit ist übrigens nur über == möglich, das einfache Gleichheitszeichen = steht immer für eine Zuweisung. Ein beliebter Fehler in C-Programmen ist das Verwechseln von = und ==. Auf Ungleichheit testen Sie mit !=, was ein durchgestrichenes = darstellen soll.

Schleifen

C kennt mehrere Schleifentypen, am häufigsten findet man die For- und die While-Schleifen. Während letztere noch eine nachvollziehbare Syntax hat, sieht die For-Schleife ungewöhnlich aus: Um etwa eine Integer-Variable von 1 bis 10 hochzuzählen, ist folgender Code nötig:

for (i=1; i<11; i++) {
  // etwas mit i tun
}

Der Code für die Schleife beginnt mit dem Schlüsselwort for, und danach folgen in runden Klammern drei Ausdrücke, die jeweils durch Semikola voneinander getrennt sind. Diese drei Teile haben folgende Bedeutung:

  • i=1: Diesen Code führt das Programm einmalig am Anfang der Schleife aus. Damit eignet sich diese Stelle dazu, einen Schleifenzähler (hier i) zu initialisieren.
  • i<11: An der zweiten Stelle steht ein Test: Nur wenn der hier zu findende Ausdruck “wahr” ergibt, führt das Programm den Code im Inneren der Schleife aus. Da die Beispielschleife von 1 bis 10 zählen soll und dazu i mehrfach um 1 erhöht, klappt das für die Werte 1, 2, 3, …, 10 – wenn nach dem letzten Durchlauf i auf 11 gesetzt wird, bricht die Schleife ab.
  • i++: An der dritten Position steht Code, der nach jedem Schleifendurchlauf abgearbeitet wird. i++ ist eine Kurzform von i = i+1, was eben die Variable i um 1 erhöht.

Alle drei Komponenten sind optional, man kann sie auch weglassen. Die kürzeste Schleifenkonstruktion ist darum for (;;) { ... }: Das erzeugt eine Endlosschleife. Eine Schleife ohne Abbruchbedingung kann man immer noch verlassen, dafür gibt es das Schlüsselwort break. Eine alternative Formulierung der obigen Beispielschleife wäre also:

i=1;
for (;;) {
  // etwas mit i tun
  i++;
  if (i>=11) break;
}

Intuitiver als die For-Schleife ist die While-Schleife, die es in zwei Varianten gibt:

while (i<10) {
  // etwas mit i tun
  // i verändern
}

Hier wird immer vor einem Schleifendurchlauf getestet, ob eine Bedingung (im Beispiel: i<10) noch erfüllt ist. Die Alternative setzt das Schlüsselwort ans Ende und benötigt ein zusätzliches do am Anfang:

do {
  // etwas mit i tun
  // i verändern
} while (i<10);

Bei dieser Schleife gibt es in jedem Fall mindestens einen Durchlauf, denn das Programm prüft erst am Ende eines Durchlaufs die Bedingung. In anderen Programmiersprachen sind solche Schleifen auch als “Repeat-Until”-Schleifen (wiederhole solange, bis …) bekannt.

Pointer

Die größte Hürde für C-Einsteiger ist in der Regel der Einsatz von Pointern. Ein Pointer oder Zeiger ist eine spezielle Variablenart, bei der die Variable nicht einen Wert, sondern eine Speicheradresse enthält. Was an dieser Adresse gespeichert ist, kann das Programm dann über den “*-Operator” auswerten. Betrachten Sie das folgende Beispiel:

int *ptr;
*ptr = 20;
printf ("Wert: %d\n", *ptr);
printf ("Adresse: %x\n", ptr);

Die Deklaration int *ptr legt fest, dass ptr ein Pointer ist, und zwar ein Pointer auf einen Integer-Wert. Das heißt, dass der Wert, der im Hauptspeicher an der Adresse ptr zu finden ist, als Ganzzahl (und nicht etwa als Fließkommazahl) zu interpretieren ist.

Um den Wert zu setzen oder zu verwenden, muss man ptr das Sternchen voranstellen – mit dem Befehl ptr = 20 (der so nicht erlaubt ist) würde man stattdessen die Adresse ändern, die sich das Programm in ptr merkt. Das kann teilweise zu unlesbarem Code führen: Wenn Sie z. B. zwei Pointer a und b haben, können Sie das Produkt der gespeicherten Werte mit *a**b berechnen, auch wenn man hier aus Gründen der Lesbarkeit eher überflüssige Klammern einführen und (*a)*(*b) schreiben sollte. Das Sternchen hat in dem Ausdruck also zwei Funktionen: Es ist das Zeichen für Multiplikation und gleichzeitig das Zeichen für den Zugriff auf den an der jeweiligen Adresse gespeicherten Wert.

Der printf-Befehl verwendet einen so genannten Format-String, um die Zahlen an einer geeigneten Stelle in der Ausgabe unterzubringen: Im Beispiel bedeutet "Wert: %d\n", dass hinter “Wert:” eine Zahl und dann der Zeilenumbruch folgen – das %d ist eine Formatangabe für Integer-Zahlen; die Tabelle Formatcodes listet noch ein paar zusätzliche Formatcodes auf. Sie können in einem printf-Aufruf auch mehrere Werte über Formatcodes integrieren, z. B. wie folgt:

printf ("%d + %d = %d\n", 3, 4, 3+4);

Das erzeugt die Ausgabe “3 + 4 = 7” (und einen Zeilenumbruch). Die Werte müssen Sie hinter dem Format-String in der Reihenfolge angeben, in der die Formatcodes im Format-String erscheinen.

Formatcodes

Code Variablentyp; Beispiel
%d Ganzzahl (int; 42)
%x Ganzzahl in Hexadezimalschreibweise (int; 42)
%ld lange Ganzzahl (long int, long; 42)
%lx lange Ganzzahl, hexadezimal (long int, long; 42)
%f Fließkommazahl (float, double; 3.1415)
%c Zeichen (char; 'a')
%s String (char*, char[]; "abcdef")
%p Adresse (&variable; &x, ptr)

Ein weiteres Zeichen taucht im Zusammenhang mit Pointern genauso oft auf: Der Adressoperator & liefert zu einer Variable ihre Adresse. Sie können darum Folgendes in einem C-Programm schreiben:

int x = 20;
int *ptr;
ptr = &x;
printf ("%d\n", *ptr);

Das erzeugt eine klassische Integer-Variable x (mit Wert 20) und einen Pointer ptr, dem ptr = &x die Adresse von x zuweist. Der printf-Befehl gibt dann über *ptr den an der Adresse gespeicherten Wert aus – also x bzw. im Beispiel 20.

Ein Anfang

Jetzt haben Sie die wichtigsten Sprachkonzepte von C gesehen – selbst in C programmieren können Sie damit noch nicht, aber es ist wahrscheinlicher, dass Sie C-Programme ansatzweise verstehen.

Wenn Sie sich tiefer in die C-Programmierung einarbeiten und eigene Programme in dieser Sprache entwickeln möchten, finden Sie im Internet zahlreiche kostenlose Einführungen, z. B. bei c-howto.de[1]: Die Seite führt Sie Schritt für Schritt durch die Sprachkonzepte; bei bookboon.com gibt es ein werbefinanziertes E-Book zu C [2], das Sie nach Registrierung als PDF-Datei herunterladen können. Wenn Sie gelernt haben, C-Programme zu lesen, verstehen Sie auch C++, Java, JavaScript, Objective C und C#: Diese Sprachen sind allesamt Weiterentwicklungen von C und verwenden eine ähnliche Syntax.

Glossar

Bibliotheken

Bibliotheken enthalten standardisierte Routinen (Programmteile), die von vielen Programmen benutzt werden können: So verwenden etwa die meisten KDE-Programme den gleichen Code zum Öffnen von Dateien – es wäre nun aber Unfug, diesen Code in jedes KDE-Programm fest einzubinden: Das würde dazu führen, dass der Code mehrfach im Hauptspeicher des Rechners liegt, wenn mehrere KDE-Programme ausgeführt werden. Stattdessen landet dieser Code in einer Bibliothek, die dann nur ein einziges Mal geladen wird und allen KDE-Programmen zur Verfügung steht. Die Bibliotheken (engl.: Libraries) liegen meistens in /usr/lib/, /usr/local/lib/ oder Unterverzeichnissen davon; einige wichtige Systembibliotheken liegen direkt in /lib.

Infos

[1] Einführung in C: http://www.c-howto.de/tutorial.html

[2] Gratisbuch über C: Thomas Theis, “Kurs in der Programmiersprache C”, http://bookboon.com/de/kurs-in-der-programmiersprache-c-ebook

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
EasyLinux 03/2013 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben