C für PICs: Funktionen



zurück zu C für PICs , C-Compiler , PIC-Prozessoren , Elektronik , Homepage

C für PICs
Grundlagen von C
Variablen
Funktionen
Operationen
Programmablaufsteuerung
Arrays und Strings
Pointer
Strukturierte Typen
PIC-spezifisches


zurück zu C für PICs


Funktionen


Der ausführbare Programmcode jedes C-Programms besteht aus Programmblöcken, die Funktionen genannt werden. So eine Funktion kann man aufrufen, dann wird der Code in dieser Funktion vom Prozessor abgearbeitet, und danach kehrt der Prozessor zum rufenden Programm zurück. Funktionen können wieder andere Funktionen aufrufen. Eigentlich könnte man auch den gesamten Code in einen einzigen Block (die main-Funktion eines C-Programms) schreiben,

main()
{
    anweisung1;
    anweisung2;
    anweisung3;
    anweisung4;
    anweisung5;
}

aber es gibt oft Programmschnipsel, die man an mehreren Stellen des Programms immer wieder benötigt.  Im folgenden Beispiel ist das die drei mal auftretende Gruppe von anweisung1 bis anweisung5.

main()
{
    anweisung1;
    anweisung2;
    anweisung3;
    anweisung4;
    anweisung5;
    _was_anderes;
    anweisung1;
    anweisung2;
    anweisung3;
    anweisung4;
    anweisung5;
    _noch_was_anderes;
    anweisung1;
    anweisung2;
    anweisung3;
    anweisung4;
    anweisung5;
    _ganz_was_anderes;
}

Anstatt diesen Code nun an jeder "bedürftigen" Stelle im Hauptprogramm einzufügen, schreibt man ihn nur ein mal in eine Funktion, und ruft dann diese eine Funktion an diesen Stellen jeweils auf. Das Programm wird dadurch kleiner, übersichtlicher und leichter zu pflegen.

anweisung1bis5()
{
    anweisung1;
    anweisung2;
    anweisung3;
    anweisung4;
    anweisung5;
}

main()
{
    anweisung1bis5();
    _was_anderes;
    anweisung1bis5();
    _noch_was_anderes;
    anweisung1bis5();
    _ganz_was_anderes;
}



Definition und Prototypen

Jedes C-Programm enthält einen Haupt-Funktion mit dem Namen main. Beim Start des PICs (power-on oder reset) wird diese Funktion aufgerufen. Im PIC sollte die Funktion main nie zu ende gehen (Endlosschleife), passiert es doch, dann wird ein Reset ausgelöst. Alle anderen Funktionen bekommen vom Programmierer einen Namen zugeteilt.
Im Folgenden sehen wir ein kleines C-Programm, das aus einer main-Funktion sowie einer zusätzliche Funktion mit dem Namen fu1 besteht. Die main-Funktion ruft die Funktion fu1 auf. Diese Funktion macht hier allerdings nichts, außer nach ihrer Beendigung (return) wieder zu main zurückzukehren. Wie wir sehen, endet damit auch main. In einem Personalcomputer wäre damit das Programm beendet, und das Betriebssystem übernimmt wieder die Kontrolle. Im PIC gibt es kein Betriebssystem, und es würde ein Reset erfolgen. Das ist hier noch ein Mangel, den ich später beheben werde.

fu1()
{
    return;
}

main()
{
    fu1();
}

Im Folgenden vertausche ich die beiden Funktionen des Programms.

main()
{
    fu1();
}

fu1()
{
    return;
}

Eigentlich scheint sich nichts geändert zu haben. Trotzdem enthält das Programm nun einen Fehler, auf den der Compiler stoßen wird. Der Compiler arbeitet des Quelltext von oben nach unten ab. Dabei stößt er innerhalb der main-Funktion auf den Aufruf der Funktion fu1. Diese kennt er aber noch nicht (während main immer bekannt ist). Weshalb er das Compilieren mit einem Fehler abbrechen wird. Funktionen sollten also immer definiert sein, bevor sie aufgerufen werden.
Um dieses Problem zu entschärfen, gibt es Funktions-Prototypen. Das sind vorgezogene Kurzbeschreibungen der Funktionen, die eigentlich nur aus dem Funktionskopf bestehen. Sie stehen am Programmanfang, wenigstens aber vor dem ersten Aufruf der Funktion. Der Compiler kennt dann beim ersten Aufruf der Funktion wenigstens ihren Namen (und die Funktions-Parameter), was ausreichend ist. Es muss dann aber im späteren Programmverlauf zwingend noch die vollständige Funktionsdefinition enthalten sein, oder es wird wieder ein Fehler gemeldet.

fu1();

main()
{
    fu1();
}

fu1()
{
    return;
}

Etwas habe ich bisher weggelassen. Nun werde ich es ergänzen. An Funktionen kann man Argumente übergeben, und eine Funktion kann auch ein Argument zurückgeben. Beides wird gleich erläutert. Wird auf solche Übergaben verzichtet (wie in unserem Beispiel), dann muss jeweils der Platzhalter void an Stelle der Argumente eingetragen werden. Das Listing sieht dann so aus:

void fu1(void);

void main(void)
{
    fu1();
}

void fu1(void)
{
    return;
}



Argumentübergabe


Innerhalb einer Funktionen werden normalerweise Daten verarbeitet. Diese Daten stehen natürlich in Variablen. Geeignet wären dafür globale Variablen, auf die von überall, also auch aus Funktionen heraus, zugegriffen werden kann.
Wesentlich flexibler können Funktionen aber genutzt werden, wenn ihnen beim Aufruf ein Wert direkt übergeben werden kann. Genau dazu dienen die runden Klammern hinter dem Funktionsnamen. Hier hinein schreibt man beim Funktionsaufruf den zu übergebenden Wert. Das kann z.B. eine Zahl sein, oder eine Variable, oder irgendein anderer Ausdruck, der einen Wert ergibt. Damit das klappt, muss der Compiler aber wissen, welchen Typ dieser Wert hat. Diese Festlegung erfolgt im Kopf der Funktionsdeklaration, wo auch schon der Funktionsname festgelegt wurde. Im nachfolgenden Beispiel wird die Funktion so definiert, dass man ihr eine integer-Zahl beim Aufruf übergeben muss. Beim Aufruf der Funktion wird dann automatisch eine lokale int Variable mit dem übergebenen Wert angelegt. In unserem Fall ist das die lokale Variable a

void fu1(int a)
{
    return;
}

Die Funktion fu1 macht zwar noch gar nichts, aber ein Programm mit dieser Funktion könnte so aussehen:

void fu1(int a);

void main(void)
{
    int b;
    b = 8;
    fu1(5);
    fu1(b);
    fu1(3+b);
}

void fu1(int a)
{
    return;
}

Im oben stehenden Beispiel wird die Funktion fu1 drei mal aufgerufen. Jedes mal wird ihr ein integer-Wert als Parameter übergeben, um verschiedene Varianten zu demonstrieren. Man beachte, dass am Programmanfang wieder ein Funktionsprototyp der Funktion fu1 steht, der ebenfalls die Definition der zu übergebenen Parameter enthält.

Man kann einer Funktion auch mehrere Parameter gleichen oder unterschiedlichen Typs übergeben:

void fu1(int a, int b, float c);

void main(void)
{
    fu1(5, 77, 22.5);
}

void fu1(int a, int b, float c)
{
    return;
}

Man beachte, dass die Parameterübergabe eine Daten-Einbahnstraße in die Funktion hinein darstellt. Eine Rückübergabe veränderter Werte aus der Funktion heraus ist damit nicht möglich.



Argumentrückgabe

Aber wie kann eine Funktion ein Ergebnis der Datenverarbeitung an den Rest des Programms zurückgeben? Zum einen gibt es da natürlich globale Variablen, die auch innerhalb einer Funktion mit Werten beschrieben werden können.
Deutlich flexibler ist aber die Argumentrückgabe. Jede gerufene Funktion kann genau einen Wert eines Typs an die aufrufende Funktion zurückgeben. Dafür betrachtet man einfach die Funktion als Ausdruck eines bestimmten Typs. Der Typ der Funktion muss natürlich in der Funktionsdeklaration (und auch im Prototypen) festgelegt werden. Der Typbezeicher wird dazu in der Deklaration dem Funktionsnamen vorangestellt. In diesem Beispiel ist es int.

int fu1(int a)
{
    return(5);
}

Die Funktion kann jetzt einen integer-Wert an die rufende Funktion zurückgeben. Das geschieht durch den return-Befehl. Dieser Befehl beendet die Funktionsabarbeitung. Der Wert des in der runden Klammer hinter return stehenden Ausdrucks wird zurückgegeben. Er muss natürlich den richtigen Typ (hier also int) haben. Die oben definierte Funktion wird also immer "5" liefern, was noch ein wenig primitiv ist.
Ich habe gerade eine tolle Idee. Man kann den numerischen Wert zweier Ausdrücke zusammenfassen!! Ich nenne das "die Addition" und werde es als Softwarepatent anmelden. Zunächst werde ich aber eine Funktion schreiben, die die Summe zweier int-Werte bildet. Diese Funktion nenne ich passender Weise "summe".

int summe(int a, int b);

void main(void)
{
    int c;
    c = summe(5, 77);
}

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

Man sieht, dass an die summe-Funktion zwei int-Argumente übergeben werden müssen. Innerhalb der Funktion stehen diese Werte dann in den lokalen Variablen a und b. Der Typ des Rückgabearguments (also der Typ der Funktion) ist ebenfalls int. Innerhalb der Funktion steht ein return-Befehl, der die Summe von a und b zurückgibt.
In main wird summe mit den integer-Argumenten 5 und 77 aufgerufen. Anstelle dieser Zahlen könnte man auch integer-Variablen einsetzen. Die summe-Funktion gibt die Summe von 5 und 77 an die rufende main-Funktion zurück. Dort wird das Ergebnis in der integer-Variablen c abgespeichert.




--> weiter zu Operationen
nach oben

zurück zu C für PICs , C-Compiler , PIC-Prozessoren , Elektronik , Homepage



Autor: sprut
erstellt: 01.10.2007
letzte Änderung: 26.04.2015