Funktionen werden nicht nur in C, sondern auch in vielen anderen Programmiersprachen verwendet. Wenn Sie von einer anderen Sprache nach C gewechselt haben, kennen Sie Funktionen möglicherweise auch unter den Namen Subroutine, Unterprogramm, Subfunktion oder ähnlich. C C++ C/C++ Funktionen Subroutine Unterprogramm Rückgabewert Parameter Argument call by value Funktionen - Subroutine Unterprogramm Rückgabewert call by value Parameter Argument Rekursion Kapitel 12: Funktionen

Funktionen werden nicht nur in C, sondern auch in vielen anderen Programmiersprachen verwendet. Wenn Sie von einer anderen Sprache nach C gewechselt haben, kennen Sie Funktionen möglicherweise auch unter den Namen Subroutine, Unterprogramm oder Subfunktion.

12.1. Was sind Funktionen            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Funktionen sind kleine Unterprogramme, mit denen Sie Teilprobleme einer größeren Aufgabe lösen können. In der Praxis können Sie sich das so vorstellen: Eine Funktion führt eine komplizierte Berechnung aus, eine andere Funktion schreibt das Ergebnis der Berechnung in eine Datei und wieder eine andere überprüft das Ergebnis auf Fehler. Die parallele Ausführung von Funktionen ist in ANSI C allerdings nicht möglich. Funktionen werden also in der Regel wie normale Anweisungen auch nur sequenziell, das heißt nacheinander, ausgeführt.

12.2. Wozu Funktionen?            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Funktionen haben eine Menge Vorteile, einige der wichtigsten sind:

Im Laufe dieses Kapitels wird noch ein wenig genauer auf die Vorteile eingegangen.

12.3. Definition von Funktionen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Die allgemeine Syntax für Funktionen sieht folgendermaßen aus:

Rückgabetyp Funktionsname(Parameter)
{
   /* Anweisungsblock mit Anweisungen */
}

Wenn Sie sich die Syntax ansehen, erkennen Sie darin vielleicht auch die Hauptfunktion main():

int main(void)

Eine Funktionsdefinition wird in folgende Bestandteile gegliedert:

12.4. Funktionsaufruf            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Nach so viel Theorie wird es Zeit für die Praxis. Zunächst wird eine einfache Funktion erstellt, die nur einen Text auf dem Bildschirm ausgibt:

void hilfe(void)
{
   printf("Ich bin die Hilfsfunktion\n");
}

Die Funktion hat keinen Rückgabetyp und keine(n) Parameter. Aufgerufen wird sie zum Beispiel mit

hilfe();

innerhalb der main()-Funktion. Sehen Sie sich den Quellcode dazu an:

#include <stdio.h>

void hilfe(void)
{
   printf("Ich bin die Hilfsfunktion\n");
}

int main()
{
   hilfe();
   return 0;
}

Durch den Aufruf von hilfe() in der main()-Funktion wird die Funktion hilfe ausgeführt und gibt einen Text auf dem Bildschirm aus.

Merke
 

Ein C-Programm beginnt immer mit der main()-Funktion. Ohne die main()-Funktion wäre ein Programm nicht lauffähig.

Eine Grafik soll dies veranschaulichen:

Abbildung 12.1: Der Ablauf bei einem Funktionsaufruf

Abbildung 12.1: Der Ablauf bei einem Funktionsaufruf

Sie können es sich etwa so vorstellen: Durch den Funktionsaufruf wird zuerst zur ersten Anweisung der Funktion gesprungen. Nach der letzten Anweisung in der Funktion können Sie sich einen Rücksprungbefehl hinzudenken. Mit diesem Rücksprungbefehl wird zur nächsten Anweisung nach dem Funktionsaufruf zurückgesprungen.

12.5. Funktionsdeklaration            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Damit der Compiler überhaupt von einer Funktion Kenntnis nimmt, muss diese vor ihrem Aufruf deklariert werden. Im vorangegangenen Beispiel ist das automatisch geschehen. Zuerst wurde die Funktion hilfe() und anschließend die main()-Funktion definiert.

Sie können aber auch Funktionen schreiben, die hinter der main()-Funktion stehen:

#include <stdio.h>

void hilfe(void);

int main()
{
   hilfe();
   return 0;
}

void hilfe(void)
{
   printf("Ich bin die Hilfsfunktion\n");
}

Da hier die Funktion hilfe() erst hinter der main()-Funktion geschrieben wurde, müssen Sie den Compiler zur Übersetzungszeit mit dieser Funktion bekannt machen. Sonst kann der Compiler in der main()-Funktion mit hilfe() nichts anfangen. Dies stellen Sie mit einer so genannten Vorwärtsdeklaration sicher:

void hilfe(void); 

Die Deklaration wird im Gegensatz zur Funktionsdefinition mit einem Semikolon abgeschlossen. Funktionsdeklarationen sollten aber nicht nur dann vorgenommen werden, wenn die Funktionen hinter die main()-Funktion geschrieben werden. Es ist ja auch möglich, dass eine Funktion andere Funktionen aufruft. Funktionen können Sie auch als unterschiedliche Formen der main()-Funktion betrachten. Sehen Sie sich im Beispiel an, was gemeint ist:

#include <stdio.h>

void func1();
void func2();
void func3();

void func1()
{
   printf("Ich bin func1 \n");
   func3();
}

void func2(void)
{
   printf("Ich bin func2 \n");
}

void func3()
{
   printf("Ich bin func3 \n");
   func2();
}

int main()
{
   func1();
   return 0;
}

Hätten Sie hier keine Vorwärtsdeklaration mit

void func1();
void func2();
void func3();

vorgenommen, hätte der Compiler beim Übersetzen (Kompilieren) Probleme mit der Funktion func1() gehabt. Denn in func1() steht der Funktionsaufruf func3(). Dem Compiler ist bis dahin eine solche Funktion noch unbekannt, da diese erst weiter unten im Quellcode implementiert wird.

Ein weiterer Grund für die Erstellung einer Vorwärtsdeklaration ist der Austausch von Funktionen über die Dateigrenze hinweg. Aber dazu später mehr.

12.6. Lokale Variablen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Die lokalste Variable ist immer die Variable im Anweisungsblock. Sehen Sie sich kurz ein Beispiel an:

#include <stdio.h>

int main()
{

   int i=333;
   if(i == 333)
      {
         int i = 111;
         printf("%d\n",i);  /* 111 */
      }
   printf("%d\n",i);   /* 333 */
   return 0;
}

Zunächst übergeben Sie am Anfang des Programms der Variable i den Wert 333. In der if-Bedingung wird der Wert auf 333 überprüft. Anschließend wird erneut eine Variable i erstellt, der Sie jetzt den Wert 111 übergeben. Die Ausgabe im Anweisungsblock von if ist somit 111. Die Ausgabe außerhalb des Anweisungsblocks if ist aber wieder 333. Aber ganz so einfach will ich es Ihnen nicht machen. Schreiben Sie das Programm nochmals neu:

#include <stdio.h>

int main()
{
   int i=333;
   if(i == 333)
      {
         i = 111;
         printf("%d\n",i);  /* 111 */
      }
   printf("%d\n",i);   /* 111 */
   return 0;
}

Sie haben hier im Gegensatz zum vorherigen Beispiel den Wert der Variable i im if-Anweisungsblock auf 111 verändert, aber keine neue lokale Variable i definiert.

Merke
 

Für lokale Variablen gilt Folgendes: Bei gleichnamigen Variablen ist immer die lokalste Variable gültig, also die, die dem Anweisungsblock am nächsten steht.

Hier nochmals ein Beispiel, das aber keine Schule machen sollte:

#include <stdio.h>

int main()
{
   int i=333;

   if(i == 333)
      {
         int i = 666;
            {
               i = 111;
               printf("%d\n",i);   /* 111 */
            }
         printf("%d\n",i);     /* 111 */
      }
   printf("%d\n",i);        /* 333 */
   return 0;
}

Lokale Variablen, die in einem Anweisungsblock definiert wurden, sind außerhalb dieses Anweisungsblocks nicht gültig.

Funktionen sind im Prinzip nichts anderes als Anweisungsblöcke. Sehen Sie sich folgendes Beispiel an:

#include <stdio.h>

void aendern()
{
   int i = 111;
   printf("In der Funktion aendern: %d\n",i);
}

int main()
{
   int i=333;
   printf("%d\n",i);
   aendern();
   printf("%d\n",i);
   return 0;
}

Hier gilt dasselbe wie schon im vorherigen Beispiel. Die neu definierte Variable i in der Funktion aendern() hat keinen Einfluss auf die gleichnamige Variable i in der main()-Funktion. Bringen Sie zum Beispiel die Funktion in die folgende Form:

void aendern()
{
   i = 111;
   printf("In der Funktion aendern: %d\n",i);
}

In diesem Fall würde sich das Programm nicht übersetzen lassen, da die Variable i noch nicht deklariert wurde. In einer Funktion müssen Sie eine Variable vor ihrer Verwendung zuerst deklarieren. Aber es gibt eine Möglichkeit, dem Compiler mitzuteilen, dass eine Variable für alle Funktionen gültig ist.

12.7. Globale Variablen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Globale Variablen können Sie sich als Vorwärtsdeklarationen von Funktionen vorstellen. Und wie der Name schon sagt, globale Variablen sind für alle Funktionen gültig. Hier ein Beispiel:

#include <stdio.h>

int i=333; /* Globale Variable */

void aendern()
{
   i = 111;
   printf("In der Funktion aendern: %d\n",i); /* 111 */
}

int main()
{
   printf("%d\n",i);  /* 333 */
   aendern();
   printf("%d\n",i);  /* 111 */
   return 0;
}

Natürlich gilt auch hier die Regel, dass bei gleichnamigen Variablen die lokalste Variable den Zuschlag erhält. Beispiel:

#include <stdio.h>

int i=333;  /* Globale Variable i */

void aendern()
{
   i = 111;   /* Ändert die globale Variable */
   printf("In der Funktion aendern: %d\n",i);  /* 111 */
}

int main()
{
   int i = 444;
   printf("%d\n",i);  /* 444 */
   aendern();
   printf("%d\n",i);  /* 444 */
   return 0;
}

In diesem Beispiel nimmt die main()-Funktion wieder keine Notiz von der globalen Variable i, da eine lokalere Variable mit demselben Namen vorhanden ist. Dabei können auch unangenehme Fehler auftreten, wenn Sie bei längeren Programmen zwei Variablen, eine globale und ein lokale, mit demselben Namen haben. Deshalb sollten Sie sich folgenden Satz zu Herzen nehmen:

Merke
 

Für das Anlegen von Variablen gilt, so lokal wie möglich und so global wie nötig.



Tipp
 

Am besten fassen Sie die Definitionen von globalen Variablen in einer zentralen C-Datei zusammen (relativ zum Programm oder Modul) und verwenden externe Deklarationen in Header-Dateien, die dann mit #include eingebunden werden, wo immer Sie die Deklarationen benötigen. (Mehr dazu in Kapitel 13, Präprozessor-Direktiven.)

12.8. Statische Variablen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Bevor ich Ihnen die statischen Variablen erklären werde, zunächst folgendes Programmbeispiel:

#include <stdio.h>

void inkrement()
{
   int i = 1;
   printf("Wert von i: %d\n",i);
   i++;
}

int main()
{
   inkrement();
   inkrement();
   inkrement();
   return 0;
}

Wenn Sie das Programm ausführen, wird dreimal ausgegeben, dass der "Wert von i: 1" ist. Obwohl man doch hier davon ausgehen muss, dass der Programmierer andere Absichten hatte, da er doch den Wert der Variable i am Ende der Funktion inkrementiert.

Dass dies nicht funktioniert, hat mit der Speicherverwaltung des Stacks zu tun. Zu diesem Thema in einem anderen Kapitel mehr.

Jetzt können Sie bei diesem Programm vor die Variable das Schlüsselwort static schreiben. Ändern Sie also diese Funktion:

void inkrement()
{
   static int i = 1;
   printf("Wert von i : %d\n",i);
   i++;
}

Damit werden für die Variable i tatsächlich die Werte 1, 2 und 3 ausgegeben. Dies haben Sie dem Schlüsselwort static zu verdanken. Denn statische Variablen verlieren bei Beendigung ihres Bezugsrahmens, also bei Beendigung der Funktion, nicht ihren Wert, sondern behalten diesen bei. Dass dies gelingt, liegt daran, dass statische Variablen nicht im Stacksegment der CPU, sondern im Datensegment gespeichert werden. Aber Achtung:

Achtung
 

Statische Variablen müssen schon bei ihrer Deklaration initialisiert werden!

12.9. Schlüsselworte für Variablen - Speicherklassen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Es gibt noch weitere Schlüsselworte in C für Variablen außer static, so genannte Speicherklassen, die hier in Kurzform erwähnt werden sollen. Das Thema Speicherklassen gehört eigentlich in das Kapitel der Variablen, hätte einen Anfänger zu Beginn aber nur verwirrt. Mit den Speicherklassen legen Sie den Geltungsbereich in der Datei, die Lebensdauer und die Bindung einer Variablen fest. Für Variablen gibt es zwei Möglichkeiten der Lebensdauer:

Jetzt folgt ein Überblick der zusätzlichen Speicherklassen-Spezifizierer (einschließlich static).

12.9.1 auto
Der Name kommt daher, dass durch das Voranstellen des Schlüsselworts auto die Variable automatisch angelegt und auch automatisch wieder gelöscht wird, ohne dass sich der Programmierer darum kümmern muss. Der Bezugsrahmen von auto ist derselbe wie bei lokalen Variablen. So ist zum Beispiel

int zahl=5;

dasselbe wie

auto int zahl=5; 

Folglich ist das Schlüsselwort auto überflüssig.

12.9.2 extern
Befindet sich die Variable in einer anderen Datei, wird das Schlüsselwort extern davor gesetzt. Diese Speicherklasse wird für Variablen verwendet, die im gesamten Programm verwendet werden können.

12.9.3 register
Dieses Schlüsselwort wird heute eigentlich überhaupt nicht mehr benutzt. Der Bezugsrahmen ist derselbe wie bei auto und lokalen Variablen. Durch Voransetzen des Schlüsselworts register weisen Sie den Compiler an, eine Variable so lange wie möglich im Prozessorregister (CPU-Register) zu halten, um dann blitzschnell darauf zugreifen zu können. Denn Prozessorregister arbeiten wesentlich schneller als Arbeitsspeicher. Allgemein wird vom Gebrauch von register abgeraten, denn ob und welche Variable der Compiler in dem schnellen Prozessorregister hält, entscheidet er letztlich selbst. Somit ist das Schlüsselwort eigentlich nicht erforderlich.

12.9.4 static
Dieses Schlüsselwort wird vor immer währenden Variablen mit einem beschränkten Geltungsbereich gesetzt.

12.10. Typ-Qualifizierer            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Außer der Speicherklasse, die Sie für eine Variable festlegen können, können Sie auch den Typ eines Objekts näher spezifizieren.

12.10.1 volatile
Mit dem Schlüsselwort volatile modifizieren Sie eine Variable so, dass der Wert dieser Variablen vor jedem Zugriff neu aus dem Hauptspeicher eingelesen werden muss.

Meistens wird volatile bei der Treiberprogrammierung eingesetzt. Hier ein Beispiel, bei dem ein Programm diejenigen Ergebnisse in einer Schleife überprüft, die ins Register der CPU abgelegt wurden:

do
   {
      printf("Gerät X wird überprüft.....\n");
   }while(reg & (STATUS_A|STATUS_B) == 0);

printf("Gerät X Status ...... [OK]\n");

Manche Compiler erkennen jetzt an der while-Schleife, dass hier immer auf die gleiche Adresse überprüft wird, und optimieren die do while-Schleife einfach weg. Dieser Vorgang wird dann nur einmal durchgeführt, da die Schleife weg ist. Wird dabei die Hardware nicht erkannt, entsteht ein Problem. Somit gilt für Variablen, die mit volatile deklariert sind, dass diese ohne jede Optimierung neu aus dem Hauptspeicher geladen und neue Werte auch sofort wieder dort abgelegt werden.

12.10.2 const
Mit diesem Typ-Qualifizierer definieren Sie eine Konstante. Dies ist eine Variable, deren Wert im Laufe der Programmausführung nicht mehr geändert werden darf.

const int wert = 10;
wert +=5;    /*  Fehler  */

12.11. Geltungsbereich von Variablen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Die Lebensdauer und der Geltungsbereich von Variablen hängen somit von zwei Punkten ab:

Je nachdem, an welcher Stelle eine Variable in einer Quelldatei deklariert wurde, gibt es folgende Geltungsbereiche:

Abbildung 12.2: Geltungsbereiche von Variablen

Abbildung 12.2: Geltungsbereiche von Variablen

Wie Sie im Abschnitt über lokale Variablen erfahren haben, gilt außerdem, dass wenn eine Variable mit demselben Namen in einem inneren Block deklariert wird, die äußere Deklaration nicht mehr sichtbar ist. Bei Verlassen des inneren Blocks ist die äußere Variable wieder sichtbar, und die innere Variable gibt es nicht mehr.

Der Geltungsbereich und die Lebensdauer von Variablen können noch auf eine andere Weise verändert werden, nämlich mithilfe des Speicherklassen-Spezifizierers. Hierzu eine Tabelle:

Position Speicherklasse Lebensdauer Geltungsbereich
In einer Funktion keine, auto, register automatisch Block
In einer Funktion extern, static statisch Block
Außerhalb Funktion keine, extern, static statisch Datei

Tabelle 12.1: Lebensdauer und Geltungsbereich von Variablen durch Speicherklassen-Spezifizierers

12.12. Speicherklassen-Spezifizierer für Funktionen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Natürlich sind die zusätzlichen Speicherklassen bei Funktionen im Vergleich zu Variablen nur beschränkt einsetzbar, da Funktionen ja nur global definiert werden können. Es folgen die Speicherklassen, die Sie den Funktionen voranstellen können, und die zugehörigen Bedeutungen.

12.12.1 extern
Wenn sie bei der Deklaration einer Funktion die Speicherklasse nicht angeben, ist diese automatisch mit extern gesetzt. Solche Funktionen können sich auch in einer anderen Quelldatei befinden. Dann speziell empfiehlt es sich, dieses Schlüsselwort zu verwenden (auch wenn dies nicht nötig wäre). Dieser Hinweis kann hilfreich für den Programmierer sein, weil er sofort weiß, worum es sich handelt. extern zu setzen dient also nicht der Verbesserung bzw. Optimierung des Quellcodes, sondern ist ein Hinweis für dessen Leser.

12.12.2 static
Weisen Sie einer Funktion das Schlüsselwort static zu, können Sie diese Funktion nur innerhalb der Datei nutzen, in der sie definiert wurde. Es ist somit das Gegenteil des Schlüsselworts extern.

12.12.3 volatile
Dies ist zwar keine Speicherklasse, sollte aber hier trotzdem erwähnt werden. Mit volatile verhindern Sie (analog zu Variablen), dass der Compiler den Quellcode optimiert und die Funktion immer wieder neu aus dem Hauptspeicher gelesen werden muss.

Nach diesen etwas theoretischen, aber wichtigen Themen geht es wieder zurück zum eigentlichen Thema des Kapitels, den Funktionen.

12.13. Datenaustausch zwischen Funktionen            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Für den Austausch von Daten kennen Sie bisher nur die Möglichkeit, globale Variablen zu verwenden. Eine globale Variable ist jeweils eine gemeinsame Variable für alle Funktionen:

#include <stdio.h>

static int zahl;
void verdoppeln(void);
void halbieren(void);

void verdoppeln(void)
{
   zahl*=2;
   printf("Verdoppelt: %d\n", zahl);
}

void halbieren(void)
{
   zahl/=2;
   printf("Halbiert: %d\n", zahl);
}

int main()
{
   int wahl;
   printf("Bitte geben Sie eine Zahl ein: ");
   scanf("%d",&zahl);

   printf("Wollen Sie diese Zahl\n");
   printf("\t1.)verdoppeln\n\t2.)halbieren\n\nIhre Wahl: ");
   scanf("%d",&wahl);

   switch(wahl)
      {
         case 1 : verdoppeln();
                  break;
         case 2 : halbieren();
                  break;
         default: printf("Unbekannte Eingabe\n");
      }
   return 0;
}

Dieses Programm weist keinerlei Fehler auf. Nur wirkt es etwas störend, dass eine globale Variable für alle Funktionen verwendet wurde. Zum einen wissen Sie, dass es besser wäre, die Variable lokal zu halten. Zum anderen kann das Programm auf diese Art schnell unübersichtlich werden. Als Beispiel ein Programm, welches sich über mehrere Dateien verteilt, bei dem Sie auf folgende Funktion stoßen:

void berechnung(void)
{
   V=l*b*h/4;
   x = V*V;
}

Es erscheint alles eindeutig, nicht wahr?! Natürlich nicht, denn es lässt sich anhand dieser Berechnung überhaupt nicht erkennen, um welchen Datentyp es sich handelt. Um also den Datentyp zu ermitteln, müssen Sie nach seiner Deklaration suchen.

Einfach ausgedrückt heißt das, dass Sie eine Funktion so programmieren müssen, dass diese auch für eine allgemeine Verwendung ausgelegt ist. Dafür gibt es in der Regel zwei Möglichkeiten, welche Sie am besten auch beide nutzen.

Mit der Wertübergabe durch Parameter übergeben Sie einer Funktion Daten (Datenfluss in die Funktion hinein), und mit der Wertrückgabe geben Sie Daten aus einer Funktion heraus (Datenfluss aus der Funktion heraus). Die Funktion arbeitet somit als eine Art Schnittstelle.

Weiter mit 12.14. Funktion mit Wertübergabe (call-by-value)            zum Inhaltsverzeichnis