Seitenleiste

Wiki

Community

Datentypen

Zählbare

Fließkomma

Datenstrukturen

Attribute

Speicherklassen

Typqualifizierer

Funktionspointer

Zeiger auf Funktionen sind ein beliebtes Tool in C um Funktionen bspw. auf Arrays auszuführen, oder um eine gewisse Art der Objektorientierung zu implementieren.

Nehmen wir doch folgendes Beispiel. Hier wollen wir die Summe aus den Einträgen eines Arrays berechnen. Die Länge wurde uns mitgegeben. Der einfach Ansatz wäre einfach eine Funktion zu schreiben, die das Array durchgeht, und die Summe berechnet:

int sum ( int* arr, int len ) {
    int s = 0;
    for ( int i = 0; i < len; i++ ) {
        s += arr[i];
    }
 
    return s;
}

Simpel. Was aber nun, wenn ich eine Multiplikation ausführen will, anstelle der Addition? Nun schreibe ich den Code für die Schleife um die eigentliche Operation herum erneut. Stattdessen könnte ich den Code für die Summenbildung in eine eigene Funktion verschieben, und die Schleife in einer eigenen Funktion belassen, die einen Funktionspointer bekommt.

Syntax

Funktionspointer in C sehen gewöhnungsbedürftig aus. Aus dem Beispiel oben, könnte eine Funktion für die Summe so aussehen:

int sum ( int result, int entry ) {
    return result + entry;
}

Wir erhalten immer den aktuellen Summenwert und den aktuellen Eintrag. Nun bilden wir einen Zeiger auf diese Funktion. Die Typangabe sieht nun so aus, hier verbinden wir damit direkt einen Namen, in einer typedef Angabe:

typedef int (*op_t)(int, int);

typedef ist aus frühreren Lektionen bekannt und erlaubt uns einem Typen einen neuen Namen zu geben. Hier geben wir dem Funktionspointer, der einen Integer zurückliefert und zwei Integer bekommt, den Namen „op_t“ (Operation-Type). Wichtig ist, damit das Ganze als Funktionspointer erkannt wird, müssen die Klammern um (*op_t).

Wollen wir das nun verwenden, so können wir:

// int map ( int *arr, int len, op_t op) 
int map ( int *arr, int len, int(*op)(int,int) ){
    int result = 0;
    for ( int i = 0; i < len; i++ ) {
        result = op(result, arr[i]);
    }
    return result;
}

Beide der Prototypangaben sind identisch (wenn sie dem obigen Typedef nachfolgen). Die Funktion map erhält das Array und die Länge und einen Funktionspointer auf eine Funktion, die ein int zurückliefert und zwei ints bekommt. Dieser Funktionspointerparameter hat den Namen op. Wir verwenden den Parameter dann einfach wir eine normale Funktion innerhalb von map.

Objektorientierung

Eine weitere Möglichkeit des Einsatzes von Funktionspointern in C ist die Objektorientierung. Beispielsweise im Linux-Kern werden oftmals Funktionspointer an Strukturen verwendet um bestimmte, auf sie abgestimmte Funktionen, zur rufen. Beispielsweise ist es etwas anderes, ob eine Datei von einem ext4 Dateisystem geladen wird, oder ob sie auf einem ntfs-Dateisystem liegt.

In Linux findet man Seitenweise solche Definitionen:

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    // ...
};
 
struct file {
    // ...
    const struct file_operations *f_ops;
    // ...
};

Ein einfacheres Beispiel

Das ist sicherlich ein sehr komplexes Beispiel, daher vereinfachen wir hier etwas, uns sagen wir haben eine Struktur, die ein Tier darstellen soll. Wir wollen zwei Implementationen dieser Struktur haben, einmal eine Katze und einen Hund. Ein Tier kann einen Laut von sich geben.

struct Animal {
    void (*make_noise)();
};

Die Struktur Animal hat also einen Funktionspointer auf eine Funktion. Nun implementieren wir Funktionen um diese Struktur zu erzeugen:

void dog_bark() {
    printf("Woof\n");
}
void cat_meow(){
    printf("Meow\n");
}
 
struct Animal MakeCat() {
    struct Animal cat;
    cat.make_noise = cat_meow;
    return cat;
}
 
struct Animal MakeDog() {
    struct Animal dog;
    dog.make_noise = dog_bark;
    return dog;
}

Nun erzeugen wir eine handvoll Tiere, legen die irgendwo ab. Rufen wir dann später in unserem Code die make_noise Methode der Struktur Animal, können wir entweder einen Hund oder eine Katze hören, je nachdem welches Tier gerade angesprochen wurde.

Aber Obacht, sollten wir eine Struktur Animal erzeugen, deren Funktionspointer wir nicht (oder auf NULL) setzen, wird unser Programm abstürzen beim Versuch diese Funktion zu rufen, da wir auf ungültigen Speicher zugreifen. Wenn wir also nicht die Erzeugung der Strukturen nicht immer selbst in der Hand haben, sollten wir lieber eine Überprüfung davorsetzen, ob die Funktion gesetzt ist, bevor wir sie rufen.