Frage

Ich hatte in letzter Zeit einige Erfahrung mit Funktionszeiger in C.

Also ging ich mit der Tradition weiter, Ihre eigenen Fragen zu beantworten, und beschloss, eine kleine Zusammenfassung der Grundlagen für diejenigen zu machen, die einen schnellen Eintauchen in das Thema benötigen.

War es hilfreich?

Lösung

Funktionszeiger in c

Beginnen wir mit einer grundlegenden Funktion, die wir sein werden zeigt auf:

int addInt(int n, int m) {
    return n+m;
}

Lassen Sie uns als erstes einen Zeiger auf eine Funktion definieren, die 2 erhält ints und gibt eine zurück int:

int (*functionPtr)(int,int);

Jetzt können wir sicher auf unsere Funktion verweisen:

functionPtr = &addInt;

Jetzt, da wir einen Zeiger auf die Funktion haben, verwenden wir ihn:

int sum = (*functionPtr)(2, 3); // sum == 5

Das Übergeben des Zeigers an eine andere Funktion ist im Grunde dasselbe:

int add2to3(int (*functionPtr)(int, int)) {
    return (*functionPtr)(2, 3);
}

Wir können auch Funktionszeiger als Rückkehrwerte verwenden (versuchen Sie, mitzuhalten, es wird chaotisch):

// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
    printf("Got parameter %d", n);
    int (*functionPtr)(int,int) = &addInt;
    return functionPtr;
}

Aber es ist viel schöner, a zu verwenden typedef:

typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef

myFuncDef functionFactory(int n) {
    printf("Got parameter %d", n);
    myFuncDef functionPtr = &addInt;
    return functionPtr;
}

Andere Tipps

Funktionszeiger in C können verwendet werden, um eine objektorientierte Programmierung in C durchzuführen.

Beispielsweise sind die folgenden Zeilen in C geschrieben:

String s1 = newString();
s1->set(s1, "hello");

Ja das -> und das Fehlen von a new Der Bediener ist ein totes Geben, aber es scheint sicher zu implizieren, dass wir den Text einiger festlegen String Klasse zu sein "hello".

Durch Verwendung von Funktionszeigern, Es ist möglich, Methoden in C zu emulieren.

Wie ist das erreicht?

Das String Klasse ist eigentlich ein struct mit einer Reihe von Funktionszeigern, die als Möglichkeit dienen, Methoden zu simulieren. Das Folgende ist eine teilweise Erklärung der String Klasse:

typedef struct String_Struct* String;

struct String_Struct
{
    char* (*get)(const void* self);
    void (*set)(const void* self, char* value);
    int (*length)(const void* self);
};

char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);

String newString();

Wie zu sehen ist, die Methoden der String Klasse sind tatsächlich Funktionszeiger auf die deklarierte Funktion. Bei der Vorbereitung der Instanz der String, das newString Die Funktion wird aufgerufen, um die Funktionszeiger auf ihre jeweiligen Funktionen einzurichten:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

Zum Beispiel die getString Funktion, die aufgerufen wird, indem Sie die aufrufen get Die Methode ist definiert wie folgt:

char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

Eine Sache, die bemerkt werden kann, ist, dass es kein Konzept für eine Instanz eines Objekts gibt und Methoden, die tatsächlich Teil eines Objekts sind, ein "Selbstobjekt" in jedem Aufruf übergeben werden muss. (Und die internal ist nur ein versteckter struct Dies wurde früher aus der Code -Auflistung weggelassen - es ist eine Möglichkeit, Informationen zu verstecken, aber das ist für Funktionszeiger nicht relevant.)

Also, anstatt es zu tun zu können s1->set("hello");, man muss das Objekt übergeben, um die Aktion aufzuführen s1->set(s1, "hello").

Wenn diese geringfügige Erklärung in einem Verweis auf sich selbst aus dem Weg gehen muss, werden wir zum nächsten Teil wechseln, was ist Vererbung in c.

Nehmen wir an, wir wollen eine Unterklasse von erstellen String, sagen wir an ImmutableString. Um die Zeichenfolge unveränderlich zu machen, die set Die Methode ist nicht zugänglich, während der Zugriff auf den Zugang zu erhalten wird get und length, und zwingen den "Konstruktor", a zu akzeptieren char*:

typedef struct ImmutableString_Struct* ImmutableString;

struct ImmutableString_Struct
{
    String base;

    char* (*get)(const void* self);
    int (*length)(const void* self);
};

ImmutableString newImmutableString(const char* value);

Grundsätzlich sind die verfügbaren Methoden für alle Unterklassen erneut Funktionszeiger. Diesmal die Erklärung für die set Methode ist nicht vorhanden, daher kann sie nicht in a aufgerufen werden ImmutableString.

Wie für die Umsetzung der ImmutableString, Der einzige relevante Code ist die Funktion "Konstruktor", die newImmutableString:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = self->base->length;

    self->base->set(self->base, (char*)value);

    return self;
}

In der Instanziation der ImmutableString, Die Funktion weckt auf die get und length Methoden beziehen sich tatsächlich auf die String.get und String.length Methode, indem Sie die durchgehen base Variable, die intern gespeichert ist String Objekt.

Die Verwendung eines Funktionszeigers kann die Vererbung einer Methode von einer Superklasse erreichen.

Wir können weiter weiterentwickeln Polymorphismus in c.

Wenn wir zum Beispiel das Verhalten des Verhaltens ändern wollten length Methode zur Rückkehr 0 die ganze Zeit in der ImmutableString Klasse Aus irgendeinem Grund müsste alles getan werden::

  1. Fügen Sie eine Funktion hinzu, die als Überschreibung dienen wird length Methode.
  2. Gehen Sie zum "Konstruktor" und setzen Sie den Funktionszeiger auf die Überschreibung length Methode.

Hinzufügen eines Überschreibens length Methode in ImmutableString kann durch Hinzufügen eines durchgeführt werden lengthOverrideMethod:

int lengthOverrideMethod(const void* self)
{
    return 0;
}

Dann der Funktionszeiger für die length Methode im Konstruktor ist an die angeschlossen lengthOverrideMethod:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = &lengthOverrideMethod;

    self->base->set(self->base, (char*)value);

    return self;
}

Jetzt, anstatt ein identisches Verhalten für die length Methode in ImmutableString Klasse als die String Klasse, jetzt die length Die Methode bezieht sich auf das in der definierte Verhalten lengthOverrideMethod Funktion.

Ich muss einen Haftungsausschluss hinzufügen, den ich immer noch lerne, wie man mit einem objektorientierten Programmierstil in C schreibe In C. war es aber mein Ziel, zu versuchen, eine von vielen Verwendungszwecken von Funktionszeigern zu veranschaulichen.

Weitere Informationen zur Durchführung von objektorientiertem Programmieren in C finden Sie unter folgenden Fragen:

Der Leitfaden zum Entlassen: So missbrauchen Sie Funktionszeiger in GCC auf X86 -Maschinen, indem Sie Ihren Code von Hand kompilieren:

Diese String-Literale sind Bytes des 32-Bit-X86-Maschinencodes. 0xC3 ist ein x86 ret Anweisung.

Sie würden diese normalerweise nicht von Hand schreiben, Sie würden in der Montagesprache schreiben und dann einen Assembler wie wie nasm Um es zu einem flachen Binärzusammenhang zusammenzustellen, das Sie in eine C -String -Literalin heben.

  1. Gibt den aktuellen Wert im EAX -Register zurück

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. Schreiben Sie eine Swap -Funktion

    int a = 10, b = 20;
    ((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
    
  3. Schreiben Sie jedes Mal einen für Schleife auf 1000 auf 1000 Funktionen auf eine Funktion

    ((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
    
  4. Sie können sogar eine rekursive Funktion schreiben, die auf 100 zählt

    const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
    i = ((int(*)())(lol))(lol);
    

Beachten Sie, dass Compiler String -Literale in der platzieren .rodata Abschnitt (oder .rdata unter Windows), das als Teil des Textsegments verknüpft ist (zusammen mit Code für Funktionen).

Das Textsegment hat die Erlaubnis von+Execes gelesen, so mprotect() oder VirtualProtect() Systemaufrufe, wie Sie einen dynamisch zugewiesenen Speicher benötigen würden. (Oder gcc -z execstack Verknüpft das Programm mit Stack + Data Segment + Heap ausführbar als kurzer Hack.)


Um diese zu zerlegen, können Sie dies zusammenstellen, um ein Etikett auf die Bytes zu setzen und einen Disassembler zu verwenden.

// at global scope
const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";

Kompilien mit gcc -c -m32 foo.c und zerlegt mit objdump -D -rwC -Mintel, Wir können die Montage erhalten und herausfinden, dass dieser Code gegen das ABI verstößt, indem wir EBX (ein Anrufvermittelt Register) klopfen und im Allgemeinen ineffizient ist.

00000000 <swap>:
   0:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]   # load int *a arg from the stack
   4:   8b 5c 24 08             mov    ebx,DWORD PTR [esp+0x8]   # ebx = b
   8:   8b 00                   mov    eax,DWORD PTR [eax]       # dereference: eax = *a
   a:   8b 1b                   mov    ebx,DWORD PTR [ebx]
   c:   31 c3                   xor    ebx,eax                # pointless xor-swap
   e:   31 d8                   xor    eax,ebx                # instead of just storing with opposite registers
  10:   31 c3                   xor    ebx,eax
  12:   8b 4c 24 04             mov    ecx,DWORD PTR [esp+0x4]  # reload a from the stack
  16:   89 01                   mov    DWORD PTR [ecx],eax     # store to *a
  18:   8b 4c 24 08             mov    ecx,DWORD PTR [esp+0x8]
  1c:   89 19                   mov    DWORD PTR [ecx],ebx
  1e:   c3                      ret    

  not shown: the later bytes are ASCII text documentation
  they're not executed by the CPU because the ret instruction sends execution back to the caller

Dieser Maschinencode funktioniert (wahrscheinlich) in 32-Bit-Code unter Windows, Linux, OS X usw. EBX wird jedoch in allen normalen Aufrufkonventionen eingerechnet. Wenn Sie es daher als Schurkenregister verwenden, ohne zu speichern/wiederherzustellen, kann es den Anrufer leicht zum Absturz bringen.

Einer meiner Lieblingsnutzungen für Funktionszeiger ist ebenso billig und einfache Iteratoren -

#include <stdio.h>
#define MAX_COLORS  256

typedef struct {
    char* name;
    int red;
    int green;
    int blue;
} Color;

Color Colors[MAX_COLORS];


void eachColor (void (*fp)(Color *c)) {
    int i;
    for (i=0; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}

int main() {
    Colors[0].name="red";
    Colors[0].red=255;
    Colors[1].name="blue";
    Colors[1].blue=255;
    Colors[2].name="black";

    eachColor(printColor);
}

Funktionszeiger werden leicht zu deklarieren, sobald Sie die grundlegenden Deklaratoren haben:

  • Ich würde: ID: Id ist a
  • Zeiger: *D: D Zeiger auf
  • Funktion: D(<parameters>): D Funktionsaufnahme <Parameter> Rückkehr

Während D ein weiterer Deklarator ist, der mit denselben Regeln erstellt wurde. Am Ende endet es irgendwo mit ID (Siehe unten für ein Beispiel), was der Name der deklarierten Entität ist. Lassen Sie uns versuchen, eine Funktion zu erstellen, die einen Zeiger auf eine Funktion bringt, die nichts nimmt und int zurückgibt, und einen Zeiger auf eine Funktion zurückgibt, die einen SHOFE nimmt und INT zurückgibt. Mit Typ-Defs ist es so

typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

Wie Sie sehen, ist es ziemlich einfach, es mit Typedefs aufzubauen. Ohne Typedefs ist es auch nicht schwer mit den oben genannten Deklaratorregeln, die konsequent angewendet werden. Wie Sie sehen, habe ich den Teil verpasst, auf den der Zeiger zeigt und die Funktion zurückgibt. Das ist es, was ganz links von der Erklärung erscheint und ist nicht von Interesse: Es wird am Ende hinzugefügt, wenn man den Deklarator bereits aufgebaut hat. Lass uns das tun. Bauen Sie es konsequent auf, zuerst wortreich - zeigen Sie die Struktur mit Verwendung [ und ]:

function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

Wie Sie sehen, kann man einen Typ vollständig beschreiben, indem sie Deklaratoren nacheinander anhängen. Die Konstruktion kann auf zwei Arten durchgeführt werden. Einer ist Bottom-up, beginnend mit dem Richtigen (Blätter) und dem Weg bis zur Kennung. Der andere Weg ist Top-Down, beginnend am Kennung und arbeitet den Weg bis zu den Blättern. Ich werde in beide Richtungen zeigen.

Prost

Der Bau beginnt mit dem Ding rechts: das Ding, das zurückgegeben wurde, das ist die Funktion, die char nimmt. Um die Deklaratoren unterschiedlich zu halten, werde ich sie zählen:

D1(char);

Einfügte den char -Parameter direkt, da er trivial ist. Hinzufügen eines Zeigers zum Deklarator durch Ersetzen D1 durch *D2. Beachten Sie, dass wir Klammern umwickeln müssen *D2. Das kann durch den Vorrang der Vorrang der Vorrang vorgenommen werden *-operator und der Funktionsbetreiber (). Ohne unsere Klammern würde der Compiler es als lesen *(D2(char p)). Aber das wäre kein einfacher Ersatz von D1 durch *D2 Natürlich mehr. Klammern sind immer in der Nähe von Deklaratoren erlaubt. Sie machen also nichts falsch, wenn Sie tatsächlich zu viel von ihnen hinzufügen.

(*D2)(char);

Der Rückgabetyp ist abgeschlossen! Lassen Sie uns nun ersetzen D2 durch den Funktionsklarator Funktionsaufnahme <parameters> Rückkehr, welches ist D3(<parameters>) worauf wir jetzt sind.

(*D3(<parameters>))(char)

Beachten Sie, dass keine Klammern benötigt werden, da wir wollen D3 Diesmal ein Funktionsdeklar und kein Zeiger-Deklarator sein. Großartig, nur noch übrig sind die Parameter dafür. Der Parameter wird genauso erledigt wie wir den Rückgabetyp nur mit char ersetzt durch void. Also werde ich es kopieren:

(*D3(   (*ID1)(void)))(char)

Ich habe ersetzt D2 durch ID1, Da wir mit diesem Parameter fertig sind (es ist bereits ein Zeiger auf eine Funktion - kein anderes Deklarator erforderlich). ID1 wird der Name des Parameters sein. Jetzt habe ich oben am Ende gesagt, dass man den Typ hinzugefügt hat, den all diese Deklarator geändert haben - den, der ganz links von jeder Erklärung erscheint. Für Funktionen wird dies zum Rückgabetyp. Für Zeiger, die auf Typ usw. angezeigt werden, ist es interessant, wenn der Typ aufgeschrieben wird, wird es in der entgegengesetzten Reihenfolge rechts erscheinen :) Wie auch immer, ersetzt es die vollständige Erklärung. Beide Male int Natürlich.

int (*ID0(int (*ID1)(void)))(char)

Ich habe die Kennung der Funktion genannt ID0 In diesem Beispiel.

Von oben nach unten

Dies beginnt am Kenner in der Beschreibung des Typs auf der Beschreibung des Typs und wickelt diesen Anklarator, während wir uns durch die rechte gehen. Beginnen mit Funktionsaufnahme <Parameter> Rückkehr

ID0(<parameters>)

Das nächste in der Beschreibung (nach "Rückkehr") war Zeiger auf. Lassen Sie es uns integrieren:

*ID0(<parameters>)

Dann war das nächste Ding Functon Taking <Parameter> Rückkehr. Der Parameter ist ein einfacher Sod, also setzen wir ihn sofort wieder ein, da er wirklich trivial ist.

(*ID0(<parameters>))(char)

Beachten Sie die Klammern, die wir hinzugefügt haben, da wir wieder wollen, dass das die * bindet zuerst und bindet und dann das (char). Sonst würde es lesen Funktionsaufnahme <Parameter> Rückkehrfunktion .... Noes, Funktionen Rückkehrfunktionen sind nicht einmal zulässig.

Jetzt müssen wir nur noch setzen <Parameter>. Ich werde eine Kurzversion der Ableitung zeigen, da ich denke, dass Sie bereits die Idee haben, wie es geht.

pointer to: *ID1
... function taking void returning: (*ID1)(void)

Einfach gesagt int Vor den Deklaratoren wie wir mit Bottom-up und wir sind fertiggestellt

int (*ID0(int (*ID1)(void)))(char)

Das Schöne

Ist Bottom-up oder Top-Down besser? Ich bin es gewohnt, Bottom-up zu verbringen, aber einige Leute können sich mit oben nach unten bequemer fühlen. Ich denke, es ist eine Frage des Geschmacks. Wenn Sie alle Betreiber in dieser Erklärung anwenden, erhalten Sie übrigens ein int:

int v = (*ID0(some_function_pointer))(some_char);

Das ist eine schöne Eigenschaft von Deklarationen in C: Die Erklärung behauptet, wenn diese Operatoren in einem Ausdruck unter Verwendung der Kennung verwendet werden, dann ergibt sie den Typ links. Es ist auch für Arrays so.

Ich hoffe, Ihnen hat dieses kleine Tutorial gefallen! Jetzt können wir uns darauf verlinken, wenn die Leute sich über die seltsame Erklärungssyntax von Funktionen wundern. Ich habe versucht, so wenig C -Interna wie möglich zu platzieren. Fühlen Sie sich frei, Dinge darin zu bearbeiten/zu beheben.

Eine weitere gute Verwendung für Funktionszeiger:
Wechsel zwischen den Versionen schmerzlos wechseln

Sie sind sehr praktisch, wenn Sie zu unterschiedlichen Zeiten oder unterschiedlichen Entwicklungsphasen unterschiedliche Funktionen wünschen. Zum Beispiel entwickle ich eine Anwendung auf einem Host -Computer mit einer Konsole, aber die endgültige Version der Software wird auf ein AVNET -Zedboard gestellt (mit Ports für Displays und Konsolen, aber sie werden für die nicht benötigt/gesucht endgültige Veröffentlichung). Während der Entwicklung werde ich also verwenden printf Um Status- und Fehlermeldungen anzuzeigen, aber wenn ich fertig bin, möchte ich nichts gedruckt. Hier ist, was ich getan habe:

Version.h

// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION


// Define which version we want to use
#define DEBUG_VERSION       // The current version
// #define RELEASE_VERSION  // To be uncommented when finished debugging

#ifndef __VERSION_H_      /* prevent circular inclusions */
    #define __VERSION_H_  /* by using protection macros */
    void board_init();
    void noprintf(const char *c, ...); // mimic the printf prototype
#endif

// Mimics the printf function prototype. This is what I'll actually 
// use to print stuff to the screen
void (* zprintf)(const char*, ...); 

// If debug version, use printf
#ifdef DEBUG_VERSION
    #include <stdio.h>
#endif

// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

#ifdef INVALID_VERSION
    // Won't allow compilation without a valid version define
    #error "Invalid version definition"
#endif

Im version.c Ich werde die 2 Funktionsprototypen definieren, die in vorhanden sind version.h

Version.C

#include "version.h"

/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return    None
*
*****************************************************************************/
void board_init()
{
    // Assign the print function to the correct function pointer
    #ifdef DEBUG_VERSION
        zprintf = &printf;
    #else
        // Defined below this function
        zprintf = &noprintf;
    #endif
}

/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return   None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
    return;
}

Beachten Sie, wie der Funktionszeiger in prototypisiert ist version.h wie

void (* zprintf)(const char *, ...);

Wenn es in der Anwendung verwiesen wird, wird es überall ausführen, wo es zeigt, was noch definiert werden muss.

Im version.c, Beachten Sie in der board_init()Funktion wo zprintf wird je nach Version, die in definiert ist, eine eindeutige Funktion (deren Funktionssignatur übereinstimmt) zugewiesen version.h

zprintf = &printf; Zprintf ruft Printf für Debugging -Zwecke auf

oder

zprintf = &noprint; Zprintf kehrt nur zurück und wird nicht unnötiger Code ausführen

Das Ausführen des Codes sieht so aus:

MainProg.C

#include "version.h"
#include <stdlib.h>
int main()
{
    // Must run board_init(), which assigns the function
    // pointer to an actual function
    board_init();

    void *ptr = malloc(100); // Allocate 100 bytes of memory
    // malloc returns NULL if unable to allocate the memory.

    if (ptr == NULL)
    {
        zprintf("Unable to allocate memory\n");
        return 1;
    }

    // Other things to do...
    return 0;
}

Der obige Code wird verwendet printf Wenn im Debug -Modus oder nichts tun, wenn im Freigabemodus. Dies ist viel einfacher als das gesamte Projekt zu durchlaufen und Code zu kommentieren oder zu löschen. Alles was ich tun muss, ist die Version in der Version zu ändern version.h Und der Code wird den Rest tun!

Funktionszeiger wird normalerweise durch definiert durch typedef, und verwendet als Param & Rückgabewert.

Die oben genannten Antworten haben bereits viel erklärt, ich gebe nur ein vollständiges Beispiel:

#include <stdio.h>

#define NUM_A 1
#define NUM_B 2

// define a function pointer type
typedef int (*two_num_operation)(int, int);

// an actual standalone function
static int sum(int a, int b) {
    return a + b;
}

// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
    return (*funp)(a, b);
}

// use function pointer as return value,
static two_num_operation get_sum_fun() {
    return &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // call function via pointer
    printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}

// test - use function pointer as param,
void test_pointer_as_param() {
    printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}

// test - use function pointer as return value,
void test_pointer_as_return_value() {
    printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}

int main() {
    test_pointer_as_variable();
    test_pointer_as_param();
    test_pointer_as_return_value();

    return 0;
}

Eine der großen Verwendungszwecke für Funktionszeiger in C besteht darin, eine zur Laufzeit ausgewählte Funktion aufzurufen. Zum Beispiel hat die C-Laufzeitbibliothek zwei Routinen. qsort und bsearch, die einen Zeiger auf eine Funktion bringen, die aufgerufen wird, um zwei Sortierungen zu vergleichen; Auf diese Weise können Sie basierend auf allen Kriterien sortieren oder suchen, die Sie verwenden möchten.

Ein sehr grundlegendes Beispiel, wenn es eine Funktion nennt print(int x, int y) Dies muss wiederum eine Funktion aufrufen (entweder add() oder sub(), die vom gleichen Typ sind) dann werden wir dem Argument für Funktion Zeiger dem Hinzufügen eines Funktionszeigers hinzufügen print() Funktion wie unten gezeigt:

#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

Die Ausgabe ist:

Wert ist: 410
Wert ist: 390

Ein Funktionszeiger ist eine Variable, die die Adresse einer Funktion enthält. Da es sich jedoch um eine Zeigervariable mit einigen eingeschränkten Eigenschaften handelt, können Sie sie so ziemlich wie jede andere Zeigervariable in Datenstrukturen verwenden.

Die einzige Ausnahme, die ich mir vorstellen kann, ist die Behandlung des Funktionszeigers als auf etwas anderes als einen einzelnen Wert. Zeigerarithmetik durch Inkrementierung oder Dekrementierung eines Funktionszeigers oder Hinzufügen/Subtrahieren eines Versatzes an einen Funktionzeiger ist kein Nutzen, da ein Funktionszeiger nur auf eine einzige Sache zeigt, dem Einstiegspunkt einer Funktion.

Die Größe einer Funktion Zeigervariablen, die Anzahl der von der Variablen besetzten Bytes, kann je nach Architektur, z. B. x32 oder x64 oder was auch immer.

Die Deklaration für eine Funktion Zeigervariable muss dieselbe Art von Informationen wie eine Funktionserklärung angeben, damit der C -Compiler die Art von Überprüfungen durchführt, die sie normalerweise durchführen. Wenn Sie in der Definition/Definition des Funktionszeigers keine Parameterliste angeben, kann der C -Compiler die Verwendung von Parametern nicht überprüfen. Es gibt Fälle, in denen diese mangelnde Überprüfung nützlich sein kann. Denken Sie jedoch nur daran, dass ein Sicherheitsnetz entfernt wurde.

Einige Beispiele:

int func (int a, char *pStr);    // declares a function

int (*pFunc)(int a, char *pStr);  // declares or defines a function pointer

int (*pFunc2) ();                 // declares or defines a function pointer, no parameter list specified.

int (*pFunc3) (void);             // declares or defines a function pointer, no arguments.

Die ersten beiden Deklarationen sind darin etwas ähnlich:

  • func ist eine Funktion, die eine nimmt int und ein char * und gibt an eine zurück int
  • pFunc ist ein Funktionszeiger, dem die Adresse einer Funktion zugewiesen wird, die eine nimmt int und ein char * und gibt an eine zurück int

Von den oben genannten könnten wir also eine Quelllinie haben, in der die Adresse der Funktion func() wird der Funktion Zeigervariable zugewiesen pFunc wie in pFunc = func;.

Beachten Sie die Syntax, die mit einer Funktion Zeigerdeklaration/Definition verwendet wird, bei der Klammern verwendet wird, um die Vorrangregeln für natürliche Bediener zu überwinden.

int *pfunc(int a, char *pStr);    // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr);  // declares a function pointer that returns an int

Mehrere verschiedene Verwendungsbeispiele

Einige Beispiele für die Verwendung eines Funktionszeigers:

int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variable
struct {                             // declare a struct that contains a function pointer
    int x22;
    int (*pFunc)(int a, char *pStr);
} thing = {0, func};                 // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr));  // declare a function pointer that points to a function that has a function pointer as an argument

In der Definition eines Funktionszeigers können Sie die Parameterlisten der variablen Länge verwenden.

int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);

Oder Sie können eine Parameterliste überhaupt nicht angeben. Dies kann nützlich sein, aber es wird dem C -Compiler die Möglichkeit beseitigt, Überprüfungen in der angegebenen Argumentliste durchzuführen.

int  sum ();      // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

C -Stilgüsse

Sie können C -Stilgüsse mit Funktionszeigern verwenden. Beachten Sie jedoch, dass ein C -Compiler möglicherweise über Schecks nachlässt oder Warnungen anstelle von Fehlern liefert.

int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum;               // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum;   // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum;     // compiler error of bad cast generated, parenthesis are required.

Vergleichen Sie den Funktionszeiger mit Gleichheit

Sie können überprüfen, ob ein Funktionszeiger einer bestimmten Funktionsadresse mit einem gleich ist if Aussage, obwohl ich mir nicht sicher bin, wie nützlich das wäre. Andere Vergleichsbetreiber scheinen noch weniger Nützlichkeit zu haben.

static int func1(int a, int b) {
    return a + b;
}

static int func2(int a, int b, char *c) {
    return c[0] + a + b;
}

static int func3(int a, int b, char *x) {
    return a + b;
}

static char *func4(int a, int b, char *c, int (*p)())
{
    if (p == func1) {
        p(a, b);
    }
    else if (p == func2) {
        p(a, b, c);      // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
    } else if (p == func3) {
        p(a, b, c);
    }
    return c;
}

Eine Reihe von Funktionszeigern

Und wenn Sie eine Reihe von Funktionszeiger haben möchten, von denen die Elemente die Argumentliste unterschieden haben, können Sie einen Funktionszeiger mit der Argumentliste definieren (nicht void Das bedeutet keine Argumente, aber nur nicht spezifiziert) so etwas wie das folgende, obwohl Sie möglicherweise Warnungen aus dem C -Compiler sehen. Dies funktioniert auch für einen Funktionszeigerparameter auf eine Funktion:

int(*p[])() = {       // an array of function pointers
    func1, func2, func3
};
int(**pp)();          // a pointer to a function pointer


p[0](a, b);
p[1](a, b, 0);
p[2](a, b);      // oops, left off the last argument but it compiles anyway.

func4(a, b, 0, func1);
func4(a, b, 0, func2);  // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);

    // iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
    func4(a, b, 0, p[i]);
}
    // iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
    (*pp)(a, b, 0);          // pointer to a function pointer so must dereference it.
    func4(a, b, 0, *pp);     // pointer to a function pointer so must dereference it.
}

C Stil namespace Verwenden von globaler struct mit Funktionszeigern

Du kannst den ... benutzen static Schlüsselwort, um eine Funktion anzugeben, deren Name Dateiumfang ist, und diese dann einer globalen Variablen zuweisen, um etwas Ähnliches zu liefern namespace Funktionalität von C ++.

Definieren Sie in einer Header -Datei eine Struktur, die unser Namespace zusammen mit einer globalen Variablen ist, die sie verwendet.

typedef struct {
   int (*func1) (int a, int b);             // pointer to function that returns an int
   char *(*func2) (int a, int b, char *c);  // pointer to function that returns a pointer
} FuncThings;

extern const FuncThings FuncThingsGlobal;

Dann in der C -Quelldatei:

#include "header.h"

// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
    return a + b;
}

static char *func2 (int a, int b, char *c)
{
    c[0] = a % 100; c[1] = b % 50;
    return c;
}

const FuncThings FuncThingsGlobal = {func1, func2};

Dies würde dann verwendet, indem der vollständige Name der globalen Strukturvariablen und des Mitgliedsnamens angegeben wird, um auf die Funktion zuzugreifen. Das const Der Modifikator wird auf dem globalen Einsatz verwendet, damit er nicht zufällig geändert werden kann.

int abcd = FuncThingsGlobal.func1 (a, b);

Anwendungsbereiche von Funktionszeigern

Eine DLL -Bibliothekskomponente könnte dem C -Stil etwas Ähnliches tun namespace Ansatz, bei dem eine bestimmte Bibliotheksschnittstelle von einer Fabrikmethode in einer Bibliotheksoberfläche angefordert wird, die die Erstellung von a unterstützt struct Mit Funktionszeiger enthält. Diese Bibliotheksschnittstelle lädt die angeforderte DLL -Version, erstellt eine Struktur mit den erforderlichen Funktionszeiger und gibt dann die Struktur zur Verwendung an den anforderenden Anrufer zurück.

typedef struct {
    HMODULE  hModule;
    int (*Func1)();
    int (*Func2)();
    int(*Func3)(int a, int b);
} LibraryFuncStruct;

int  LoadLibraryFunc LPCTSTR  dllFileName, LibraryFuncStruct *pStruct)
{
    int  retStatus = 0;   // default is an error detected

    pStruct->hModule = LoadLibrary (dllFileName);
    if (pStruct->hModule) {
        pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
        pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
        pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
        retStatus = 1;
    }

    return retStatus;
}

void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
    if (pStruct->hModule) FreeLibrary (pStruct->hModule);
    pStruct->hModule = 0;
}

Und dies könnte wie in:

LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
//  ....
myLib.Func1();
//  ....
FreeLibraryFunc (&myLib);

Der gleiche Ansatz kann verwendet werden, um eine abstrakte Hardwareschicht für Code zu definieren, die ein bestimmtes Modell der zugrunde liegenden Hardware verwendet. Funktionszeiger werden von einer Fabrik mit hardwarespezifischen Funktionen ausgefüllt, um die im abstrakte Hardwaremodell angegebene Funktionen für hardwarespezifische Funktionen bereitzustellen. Dies kann verwendet werden, um eine abstrakte Hardwareschicht bereitzustellen, die von Software verwendet wird, die eine Werksfunktion aufruft, um die spezifische Hardware -Funktionschnittstelle zu erhalten .

Funktionszeiger, um Delegierte, Handler und Rückrufe zu erstellen

Sie können Funktionszeiger verwenden, um eine Aufgabe oder Funktionalität zu delegieren. Das klassische Beispiel in C ist der Vergleichsdelegierte -Funktionszeiger, der mit den Standard -C -Bibliotheksfunktionen verwendet wird qsort() und bsearch() Bereitstellung der Zusammenstellung zur Sortierung einer Liste von Elementen oder zur Durchführung einer binären Suche über eine sortierte Liste von Elementen. Die Vergleichsfunktionsdelegat gibt den Kollationsalgorithmus an, der in der Sortierung oder der binären Suche verwendet wird.

Eine andere Verwendung ähnelt der Anwendung eines Algorithmus auf einen C ++ - Standard -Vorlagenbibliotheksbehälter.

void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for ( ; pList < pListEnd; pList += sizeItem) {
        p (pList);
    }

    return pArray;
}

int pIncrement(int *pI) {
    (*pI)++;

    return 1;
}

void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for (; pList < pListEnd; pList += sizeItem) {
        p(pList, pResult);
    }

    return pArray;
}

int pSummation(int *pI, int *pSum) {
    (*pSum) += *pI;

    return 1;
}

// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;

ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);

Ein weiteres Beispiel ist der GUI -Quellcode, in dem ein Handler für ein bestimmtes Ereignis durch Bereitstellung eines Funktionszeigers registriert wird, der tatsächlich auf dem Laufenden des Ereignisses aufgerufen wird. Das Microsoft MFC -Framework mit seinen Meldungskarten verwendet etwas Ähnliches wie Windows -Nachrichten, die an ein Fenster oder Thread geliefert werden.

Asynchrone Funktionen, die einen Rückruf erfordern, ähneln einem Ereignishandler. Der Benutzer der asynchronen Funktion ruft die asynchrone Funktion auf, um eine Aktion zu starten, und liefert einen Funktionszeiger, den die asynchrone Funktion aufruft, sobald die Aktion abgeschlossen ist. In diesem Fall ist das Ereignis die asynchrone Funktion, die seine Aufgabe erfüllt.

Das Starten von Kratzern hat eine Speicheradresse, von der sie ausführen. In der Assemblersprache werden sie bezeichnet (aufrufen "Funktion der Funktion der Funktion".

1. Zuerst müssen Sie einen Zeiger für die Funktion erklären. 2. Gassen Sie die Adresse der gewünschten Funktion

**** Hinweis-> Die Funktionen sollten vom gleichen Typ sein ****

Dieses einfache Programm wird alles veranschaulichen.

#include<stdio.h>
void (*print)() ;//Declare a  Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
                //The Functions should Be of Same Type
int main()
{
 print=sayhello;//Addressof sayhello is assigned to print
 print();//print Does A call To The Function 
 return 0;
}

void sayhello()
{
 printf("\n Hello World");
}

enter image description hereDanach sehen Sie, wie Maschine sie versteht.

Der Bereich Red Mark zeigt, wie die Adresse ausgetauscht und in EAX gespeichert wird. Dann ist es eine Anrufanweisung zu EAX. EAX enthält die gewünschte Adresse der Funktion.

Da Funktionszeiger häufig getippte Rückrufe sind, möchten Sie sich vielleicht ansehen Geben Sie sichere Rückrufe ein. Gleiches gilt für Einstiegspunkte usw. von Funktionen, die keine Rückrufe sind.

C ist ziemlich launisch und gleichzeitig verzeihend :)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top