it-swarm.com.de

Wie funktionieren Funktionszeiger in C?

Ich hatte in letzter Zeit einige Erfahrungen mit Funktionszeigern in C.

Ausgehend von der Tradition, Ihre eigenen Fragen zu beantworten, habe ich mich entschlossen, eine kleine Zusammenfassung der Grundlagen zu erstellen, für diejenigen, die einen schnellen Einstieg in das Thema benötigen.

1139
Yuval Adam

Funktionszeiger in C

Beginnen wir mit einer Grundfunktion, die wir sein werden zeigt auf:

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

Definieren wir als erstes einen Zeiger auf eine Funktion, die 2 ints empfängt und ein int zurückgibt:

int (*functionPtr)(int,int);

Jetzt können wir sicher auf unsere Funktion verweisen:

functionPtr = &addInt;

Nachdem wir nun einen Zeiger auf die Funktion haben, verwenden wir sie:

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 Funktionszeiger auch in Rückgabewerten 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, ein typedef zu verwenden:

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;
}
1379
Yuval Adam

Funktionszeiger in C können zur objektorientierten Programmierung in C verwendet werden.

Die folgenden Zeilen sind beispielsweise in C geschrieben:

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

Ja, der _->_ und das Fehlen eines new -Operators sind ein totes Geschenk, aber es scheint zu implizieren, dass wir den Text einer String-Klasse auf _"hello"_ setzen.

Mit Hilfe von Funktionszeigern ist es möglich, Methoden in C zu emulieren.

Wie wird das erreicht?

Die String-Klasse ist eigentlich eine struct-Klasse mit einer Reihe von Funktionszeigern, mit denen Methoden simuliert werden können. Das Folgende ist eine Teildeklaration der Klasse String:

_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, sind die Methoden der Klasse String tatsächlich Funktionszeiger auf die deklarierte Funktion. Bei der Vorbereitung der Instanz von String wird die Funktion newString aufgerufen, um die Funktionszeiger auf ihre jeweiligen Funktionen zu setzen:

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

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

    self->set(self, "");

    return self;
}
_

Beispielsweise ist die Funktion getString, die durch Aufrufen der Methode get aufgerufen wird, wie folgt definiert:

_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, so dass bei jedem Aufruf ein "Selbstobjekt" übergeben werden muss. (Und das internal ist nur ein verstecktes struct, das zuvor in der Codeauflistung weggelassen wurde - es ist eine Möglichkeit, Informationen auszublenden, aber das ist für Funktionszeiger nicht relevant.)

Anstatt also s1->set("hello"); ausführen zu können, muss das Objekt übergeben werden, um die Aktion für s1->set(s1, "hello") auszuführen.

Da diese kleine Erklärung einen Hinweis auf sich selbst enthalten muss, werden wir zum nächsten Teil übergehen, bei dem es sich um die Vererbung in C handelt.

Angenommen, wir möchten eine Unterklasse von String erstellen, sagen wir eine ImmutableString. Um die Zeichenfolge unveränderlich zu machen, kann auf die Methode set nicht zugegriffen werden, während der Zugriff auf get und length erhalten bleibt. Erzwingen Sie, dass der Konstruktor ein _char*_ akzeptiert:

_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 wieder Funktionszeiger. Dieses Mal ist die Deklaration für die set-Methode nicht vorhanden, daher kann sie nicht in einer ImmutableString-Methode aufgerufen werden.

Bezüglich der Implementierung von ImmutableString ist der einzige relevante Code die "Konstruktor" -Funktion, 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;
}
_

Beim Instanziieren von ImmutableString verweisen die Funktionszeiger auf die Methoden get und length auf die Methoden _String.get_ und _String.length_, indem sie die Variable base durchlaufen, die ein intern gespeichertes Objekt String ist.

Die Verwendung eines Funktionszeigers kann die Vererbung einer Methode von einer Oberklasse bewirken.

Wir können den Polymorphismus in C fortsetzen.

Wenn wir zum Beispiel das Verhalten der length-Methode so ändern möchten, dass in der ImmutableString-Klasse immer _0_ zurückgegeben wird, müssen Sie lediglich Folgendes tun:

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

Das Hinzufügen einer überschreibenden length-Methode in ImmutableString kann durch Hinzufügen einer lengthOverrideMethod-Methode erfolgen:

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

Dann wird der Funktionszeiger für die Methode length im Konstruktor mit der Methode lengthOverrideMethod verknüpft:

_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;
}
_

Anstatt dasselbe Verhalten für die length-Methode in der ImmutableString-Klasse wie für die String-Klasse zu haben, bezieht sich die length-Methode jetzt auf das in der lengthOverrideMethod-Funktion definierte Verhalten.

Ich muss einen Haftungsausschluss hinzufügen, der besagt, dass ich noch lerne, wie man mit einem objektorientierten Programmierstil in C schreibt. Es gibt also möglicherweise Punkte, die ich nicht gut erklärt habe oder die möglicherweise falsch sind, um _ zu implementieren.OOP in C. Aber ich wollte versuchen, eine von vielen Verwendungen von Funktionszeigern zu veranschaulichen.

Weitere Informationen zum Ausführen der objektorientierten Programmierung in C finden Sie in den folgenden Fragen:

286
coobird

Die Anleitung zum Auslösen: So missbrauchen Sie Funktionszeiger in GCC auf x86-Computern, indem Sie Ihren Code von Hand kompilieren:

Diese Zeichenfolgenliterale sind Bytes des 32-Bit-x86-Maschinencodes. 0xC3 ist eine x86 ret Anweisung .

Normalerweise würden Sie diese nicht von Hand schreiben. Sie würden in Assembler schreiben und dann einen Assembler wie nasm verwenden, um sie zu einer flachen Binärdatei zusammenzusetzen, die Sie in ein C-String-Literal hexdumpten.

  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 einen for-Schleifenzähler auf 1000 und rufen Sie jedes Mal eine Funktion auf

    ((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 bis 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 Zeichenfolgenliterale im Abschnitt .rodata (oder .rdata unter Windows) platzieren, der als Teil des Textsegments (zusammen mit Code für Funktionen) verknüpft ist.

Das Textsegment verfügt über die Berechtigung "Lesen + Ausführen", sodass das Umwandeln von Zeichenfolgenliteralen in Funktionszeiger ohne die Verwendung von mprotect()- oder VirtualProtect()-Systemaufrufen funktioniert, wie sie für dynamisch zugewiesenen Speicher erforderlich sind. (Oder gcc -z execstack verknüpft das Programm als schnellen Hack mit Stack + Datensegment + ausführbarer Heap-Datei.)


Um diese zu disassemblieren, können Sie dies kompilieren, um die Bytes zu beschriften, und einen Disassembler 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";

Wenn wir mit gcc -c -m32 foo.c kompilieren und mit objdump -D -rwC -Mintel zerlegen, können wir die Assembly abrufen und feststellen, dass dieser Code gegen die ABI verstößt, indem wir EBX (ein durch Aufrufe geschütztes Register) blockieren und im Allgemeinen ineffizient sind.

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 Computercode wird (wahrscheinlich) in 32-Bit-Code unter Windows, Linux, OS X usw. funktionieren: Die Standardaufrufkonventionen für alle diese Betriebssysteme übergeben args auf dem Stapel, anstatt effizienter in Registern zu arbeiten. Aber EBX wird in allen normalen Anrufkonventionen für Anrufe beibehalten. Wenn Sie es also als Scratch-Register verwenden, ohne es zu speichern oder wiederherzustellen, kann der Anrufer leicht abstürzen.

215
Lee

Eine meiner Lieblingsanwendungen für Funktionszeiger sind so billige 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);
}
105
Nick Van Brunt

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

  • id: ID: ID ist a
  • Zeiger: *D: D Zeiger auf
  • Funktion: D(<parameters>): D-Funktion, die <parameters> returning nimmt

Während D ein weiterer Deklarator ist, der nach denselben Regeln erstellt wurde. Irgendwo endet es mit ID (siehe unten für ein Beispiel), dem Namen der deklarierten Entität. Versuchen wir, eine Funktion zu erstellen, die einen Zeiger auf eine Funktion nimmt, die nichts nimmt und int zurückgibt, und einen Zeiger auf eine Funktion zurückgibt, die ein Zeichen nimmt und int zurückgibt. Bei Typ-Defs ist das 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 konsistent angewendet werden. Wie Sie sehen, habe ich den Teil verpasst, auf den der Zeiger zeigt, und das, was die Funktion zurückgibt. Das steht ganz links in der Deklaration und ist nicht von Interesse: Es wird am Ende hinzugefügt, wenn man den Deklarator bereits aufgebaut hat. Lass uns das tun. Es konsequent aufbauen, zuerst wortreich - die Struktur mit [ und ] zeigen:

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 man nacheinander Deklaratoren anfügt. Die Konstruktion kann auf zwei Arten erfolgen. Man ist von unten nach oben, beginnend mit dem richtigen Ding (Blätter) und arbeitet sich bis zum Identifikator durch. Der andere Weg führt von oben nach unten, beginnend mit dem Identifikator, bis hinunter zu den Blättern. Ich zeige beide Wege.

Prost

Der Aufbau beginnt mit dem Ding auf der rechten Seite: Das zurückgegebene Ding ist die Funktion, die Zeichen annimmt. Um die Deklaratoren voneinander zu unterscheiden, werde ich sie nummerieren:

D1(char);

Der Parameter char wurde direkt eingefügt, da er trivial ist. Hinzufügen eines Zeigers zum Deklarator durch Ersetzen von D1 durch *D2. Beachten Sie, dass wir *D2 in Klammern setzen müssen. Dies kann durch Nachschlagen der Rangfolge von *-operator und des Funktionsaufruf-Operators () festgestellt werden. Ohne unsere Klammern würde der Compiler es als *(D2(char p)) lesen. Aber das wäre natürlich kein klares Ersetzen von D1 durch *D2 mehr. Klammern um Deklaratoren sind immer erlaubt. Sie können also nichts falsch machen, wenn Sie zu viel davon hinzufügen.

(*D2)(char);

Rückgabetyp ist vollständig! Ersetzen wir nun D2 durch den Funktionsdeklarator Funktion nimmt <parameters> und kehrt zurück, was D3(<parameters>) ist, woran wir uns gerade befinden.

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

Beachten Sie, dass keine Klammern erforderlich sind, da wir wollenD3 diesmal ein Funktionsdeklarator und kein Zeigerdeklarator sein sollen. Toll, nur die Parameter dafür sind noch übrig. Der Parameter wird genauso ausgeführt wie der Rückgabetyp, nur dass char durch void ersetzt wird. Also kopiere ich es:

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

Ich habe D2 durch ID1 ersetzt, da wir mit diesem Parameter fertig sind (es ist bereits ein Zeiger auf eine Funktion - kein weiterer Deklarator erforderlich). ID1 ist der Name des Parameters. Jetzt habe ich oben am Ende gesagt, dass man den Typ hinzufügt, den alle diese Deklaratoren modifizieren - den, der ganz links von jeder Deklaration steht. Für Funktionen wird dies zum Rückgabetyp. Für Zeiger, die auf Typ usw. zeigen ... Es ist interessant, wenn Sie den Typ aufschreiben. Er wird in umgekehrter Reihenfolge ganz rechts angezeigt. Wenn Sie ihn jedoch ersetzen, erhalten Sie die vollständige Deklaration. Beide Male int natürlich.

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

Ich habe in diesem Beispiel den Bezeichner der Funktion ID0 aufgerufen.

Von oben nach unten

Dies beginnt bei der Kennung ganz links in der Beschreibung des Typs und schließt diesen Deklarator ein, während wir uns durch die rechte Spalte bewegen. Beginnen Sie mit Funktion mit <parameters> returning

ID0(<parameters>)

Das Nächste in der Beschreibung (nach "Zurückkehren") war Zeiger auf. Lass es uns einbauen:

*ID0(<parameters>)

Dann war das nächste was Funktion nimmt <Parameter> zurück. Der Parameter ist ein einfaches Zeichen, daher geben wir ihn sofort wieder ein, da er wirklich trivial ist.

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

Beachten Sie die Klammern, die wir hinzugefügt haben, da wir erneut möchten, dass der * zuerst und dann der (char) gebunden wird. Andernfalls würde es Funktion, die <parameters> zurückgibt, lesen .... Nein, Funktionen, die Funktionen zurückgeben, sind nicht einmal erlaubt.

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

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

Stellen Sie int einfach vor die Deklaratoren, wie wir es mit Bottom-up gemacht haben, und wir sind fertig

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

Das Schöne daran

Ist Bottom-Up oder Top-Down besser? Ich bin es gewohnt, von unten nach oben zu gehen, aber manche Leute mögen es mit von oben nach unten bequemer haben. Ich denke, es ist Geschmackssache. Übrigens, wenn Sie alle Operatoren in dieser Deklaration anwenden, erhalten Sie einen int:

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

Dies ist eine Nice-Eigenschaft von Deklarationen in C: Die Deklaration besagt, dass, wenn diese Operatoren in einem Ausdruck unter Verwendung des Bezeichners verwendet werden, der Typ ganz links angegeben wird. Das ist auch bei Arrays so.

Hoffe dir hat dieses kleine Tutorial gefallen! Jetzt können wir darauf verweisen, wenn sich die Leute über die seltsame Deklarationssyntax von Funktionen wundern. Ich habe versucht, so wenig C-Einbauten wie möglich zu machen. Fühlen Sie sich frei, Dinge darin zu bearbeiten/zu reparieren.

Eine weitere gute Verwendung für Funktionszeiger:
Problemloses Wechseln zwischen Versionen

Sie sind sehr praktisch, wenn Sie unterschiedliche Funktionen zu unterschiedlichen Zeiten oder in unterschiedlichen Entwicklungsphasen benötigen. Ich entwickle zum Beispiel eine Anwendung auf einem Host-Computer mit einer Konsole. Die endgültige Version der Software wird jedoch auf einem Avnet ZedBoard gespeichert (mit Anschlüssen für Displays und Konsolen, die jedoch für das nicht benötigt/gewünscht werden) endgültige Veröffentlichung). Während der Entwicklung werde ich printf verwenden, um Status- und Fehlermeldungen anzuzeigen, aber wenn ich fertig bin, möchte ich nicht, dass etwas gedruckt wird. Folgendes habe ich getan:

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

In version.c werde ich die 2 Funktionsprototypen definieren, die in version.h vorhanden sind.

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 version.h als Prototyp erstellt wurde

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

Wenn in der Anwendung auf sie verwiesen wird, wird sie ausgeführt, wo immer sie zeigt, was noch definiert werden muss.

Beachten Sie in version.c in der Funktion board_init(), dass zprintf abhängig von der in version.h definierten Version eine eindeutige Funktion (deren Funktionssignatur übereinstimmt) zugewiesen wird.

zprintf = &printf; zprintf ruft printf zum Debuggen auf

oder

zprintf = &noprint; zprintf kehrt gerade zurück und führt keinen unnötigen Code aus

Der Code sieht dann 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;
}

Im obigen Code wird printf verwendet, wenn der Debug-Modus aktiviert ist. Dies ist viel einfacher, als das gesamte Projekt durchzugehen und Code auszukommentieren oder zu löschen. Alles was ich tun muss, ist die Version in version.h zu ändern und der Code wird den Rest erledigen!

23
Zack Sheffield

Funktionszeiger wird normalerweise durch typedef definiert und als Parameter- und Rückgabewert verwendet.

Die obigen 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;
}
14
Eric Wang

Eine der wichtigsten Verwendungsmöglichkeiten für Funktionszeiger in C besteht darin, eine zur Laufzeit ausgewählte Funktion aufzurufen. Beispielsweise verfügt die C-Laufzeitbibliothek über zwei Routinen qsort und bsearch , die einen Zeiger auf eine Funktion setzen, die aufgerufen wird, um zwei zu sortierende Elemente zu vergleichen. Auf diese Weise können Sie nach beliebigen Kriterien sortieren bzw. suchen.

Ein sehr einfaches Beispiel: Wenn es eine Funktion namens print(int x, int y) gibt, für die möglicherweise eine Funktion aufgerufen werden muss (entweder add() oder sub(), die vom gleichen Typ sind), was wir wollen Dazu fügen wir der Funktion print() ein Funktionszeigerargument hinzu, 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

13
Vamsi

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

Die einzige Ausnahme, die ich mir vorstellen kann, ist die Behandlung des Funktionszeigers als Hinweis auf etwas anderes als einen einzelnen Wert. Das Ausführen von Zeigerarithmetik durch Inkrementieren oder Dekrementieren eines Funktionszeigers oder Hinzufügen/Subtrahieren eines Offsets zu einem Funktionszeiger ist nicht wirklich nützlich, da ein Funktionszeiger nur auf eine einzelne Sache zeigt, den Einstiegspunkt einer Funktion.

Die Größe einer Funktionszeigervariablen, die Anzahl der von der Variablen belegten Bytes, kann in Abhängigkeit von der zugrunde liegenden Architektur variieren, z. x32 oder x64 oder was auch immer.

Die Deklaration für eine Funktionszeigervariable muss dieselbe Art von Informationen wie eine Funktionsdeklaration angeben, damit der C-Compiler die normalerweise durchgeführten Überprüfungen durchführen kann. Wenn Sie in der Deklaration/Definition des Funktionszeigers keine Parameterliste angeben, kann der C-Compiler die Verwendung von Parametern nicht überprüfen. Es gibt Fälle, in denen diese fehlende Überprüfung nützlich sein kann. Denken Sie jedoch 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 Erklärungen sind insofern ähnlich:

  • func ist eine Funktion, die einen int und einen char * annimmt und einen int zurückgibt.
  • pFunc ist ein Funktionszeiger, dem die Adresse einer Funktion zugewiesen wird, die einen int und einen char * annimmt und einen int zurückgibt.

Wir könnten also von oben eine Quellzeile haben, in der die Adresse der Funktion func() der Funktionszeigervariablen pFunc wie in pFunc = func; zugewiesen wird.

Beachten Sie die Syntax, die mit einer Funktionszeigerdeklaration/-definition verwendet wird, in der Klammern verwendet werden, um die natürlichen Operatorprioritätsregeln 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

Verschiedene Anwendungsbeispiele

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

Bei der Definition eines Funktionszeigers können Sie Parameterlisten variabler Länge verwenden.

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

Oder Sie können überhaupt keine Parameterliste angeben. Dies kann nützlich sein, verhindert jedoch, dass der C-Compiler die bereitgestellte Argumentliste überprüft.

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-Darsteller

Sie können C-Casts mit Funktionszeigern verwenden. Beachten Sie jedoch, dass ein C-Compiler bei Überprüfungen möglicherweise nachlässig ist oder eher Warnungen als Fehler ausgibt.

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.

Funktionszeiger mit Gleichheit vergleichen

Sie können mit einer if-Anweisung überprüfen, ob ein Funktionszeiger einer bestimmten Funktionsadresse entspricht, obwohl ich nicht sicher bin, wie nützlich das wäre. Andere Vergleichsoperatoren scheinen noch weniger nützlich zu sein.

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;
}

Ein Array von Funktionszeigern

Und wenn Sie ein Array von Funktionszeigern haben möchten, von denen jedes Element der Argumentliste Unterschiede aufweist, können Sie einen Funktionszeiger definieren, dessen Argumentliste nicht spezifiziert ist (nicht void, was bedeutet, dass keine Argumente, sondern nur nicht spezifiziert sind) Möglicherweise werden Warnungen vom C-Compiler angezeigt. 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 style namespace Using Global struct with Function Pointers

Mit dem Schlüsselwort static können Sie eine Funktion angeben, deren Name dem Dateibereich entspricht, und diese dann einer globalen Variablen zuweisen, um etwas Ähnliches wie die namespace-Funktionalität von C++ bereitzustellen.

Definieren Sie in einer Header-Datei eine Struktur, die unser Namespace ist, sowie eine globale Variable, 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 durch Angabe des vollständigen Namens der globalen Strukturvariablen und des Mitgliedsnamens für den Zugriff auf die Funktion verwendet. Der Modifikator const wird global verwendet, damit er nicht versehentlich geändert werden kann.

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

Anwendungsbereiche von Funktionszeigern

Eine DLL -Bibliothekskomponente könnte etwas Ähnliches wie der C-Stil namespace tun, bei dem eine bestimmte Bibliotheksschnittstelle von einer Factory-Methode in einer Bibliotheksschnittstelle angefordert wird, die die Erstellung einer struct -haltigen Funktionszeiger unterstützt. Diese Bibliothek Die Schnittstelle lädt die angeforderte DLL -Version, erstellt eine Struktur mit den erforderlichen Funktionszeigern und gibt sie dann zur Verwendung an den anfragenden Aufrufer 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 verwendet werden als in:

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

Mit demselben Ansatz kann eine abstrakte Hardwareschicht für Code definiert werden, die ein bestimmtes Modell der zugrunde liegenden Hardware verwendet. Funktionszeiger werden von einer Fabrik mit hardwarespezifischen Funktionen gefüllt, um die hardwarespezifische Funktionalität bereitzustellen, die die im abstrakten Hardwaremodell angegebenen Funktionen implementiert. Dies kann verwendet werden, um eine abstrakte Hardwareschicht bereitzustellen, die von einer Software verwendet wird, die eine Factory-Funktion aufruft, um die spezifische Hardwarefunktionsschnittstelle abzurufen, und dann die bereitgestellten Funktionszeiger verwendet, um Aktionen für die zugrunde liegende Hardware auszuführen, ohne dass Implementierungsdetails über das spezifische Ziel bekannt sein müssen .

Funktionszeiger zum Erstellen von Delegaten, Handlern und Rückrufen

Sie können Funktionszeiger verwenden, um Aufgaben oder Funktionen zu delegieren. Das klassische Beispiel in C ist der Funktionszeiger für Vergleichsdelegierte, der mit den Standard-C-Bibliotheksfunktionen qsort() und bsearch() verwendet wird, um die Sortierreihenfolge zum Sortieren einer Liste von Elementen oder zum Durchführen einer binären Suche über eine sortierte Liste von bereitzustellen Artikel. Der Vergleichsfunktionsdelegierte gibt den bei der Sortierung oder der binären Suche verwendeten Kollatierungsalgorithmus an.

Eine andere Verwendung ähnelt dem Anwenden eines Algorithmus auf einen C++ Standard Template Library-Container.

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 registriert wird, indem ein Funktionszeiger bereitgestellt wird, der tatsächlich aufgerufen wird, wenn das Ereignis eintritt. Das Microsoft MFC-Framework mit seinen Nachrichtenzuordnungen verwendet etwas Ähnliches, um Windows-Nachrichten zu verarbeiten, die an ein Fenster oder einen Thread übermittelt 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 stellt einen Funktionszeiger bereit, den die asynchrone Funktion aufruft, sobald die Aktion abgeschlossen ist. In diesem Fall ist das Ereignis die asynchrone Funktion, die ihre Aufgabe erfüllt.

5

Die Funktion "Von vorne anfangen" weist einige Speicheradressen auf, von denen aus sie ausgeführt werden. In der Assemblersprache heißen sie (rufen Sie die "Speicheradresse der Funktion" auf). Kehren Sie jetzt zu C zurück. Wenn die Funktion eine Speicheradresse hat, können sie von Zeigern in C.So nach den Regeln von C bearbeitet werden

1.Zuerst müssen Sie einen Zeiger auf die Funktion deklarieren. 2.Geben Sie die Adresse der gewünschten Funktion ein

**** Hinweis-> die Funktionen sollten vom selben 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 können Sie sehen, wie die Maschine sie versteht. Einblick in die Maschinenanweisung des obigen Programms in einer 32-Bit-Architektur.

Der rote Markierungsbereich zeigt an, wie die Adresse ausgetauscht und in eax gespeichert wird. Dann gibt es eine Call-Anweisung für eax. eax enthält die gewünschte Adresse der Funktion.

4
Mohit Dabas

Da es sich bei Funktionszeigern häufig um typisierte Rückrufe handelt, sollten Sie sich type safe callbacks ansehen. Dasselbe gilt für Einstiegspunkte usw. von Funktionen, die keine Rückrufe sind.

C ist ziemlich launisch und verzeihend zugleich :)

0
Tim Post