c - snippet - title tag




Wie funktionieren Funktionszeiger in C? (8)

Funktionszeiger in C

Beginnen wir mit einer grundlegenden Funktion, auf die wir zeigen werden :

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

Als erstes definieren wir einen Zeiger auf eine Funktion, die 2 int s empfängt und einen int zurückgibt:

int (*functionPtr)(int,int);

Jetzt können wir sicher auf unsere Funktion hinweisen:

functionPtr = &addInt;

Nun, da wir 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 unordentlich):

// 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, einen 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;
}

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

Um die eigenen Fragen beantworten zu können, habe ich beschlossen, eine kurze Zusammenfassung der Grundlagen für diejenigen zu erstellen, die einen schnellen Einstieg in das Thema benötigen.


Eine weitere gute Verwendung für Funktionszeiger:
Zwischen den Versionen schmerzfrei wechseln

Sie sind sehr praktisch, wenn Sie verschiedene Funktionen zu unterschiedlichen Zeiten oder in unterschiedlichen Entwicklungsphasen verwenden möchten. Zum Beispiel entwickle ich eine Anwendung auf einem Host - Computer, der eine Konsole hat, aber die endgültige Version der Software wird auf ein Avnet ZedBoard gesetzt (welches Ports für Displays und Konsolen hat, aber für die endgültige Freigabe). Während der Entwicklung werde ich printf , um Status- und Fehlermeldungen anzuzeigen, aber wenn ich fertig bin, möchte ich nichts drucken. 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 ich die 2 Funktionsprototypen definieren, die in 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, dass der Funktionszeiger in version.h als Prototyp erstellt wird

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

Wenn es in der Anwendung referenziert wird, wird es ausgeführt, wo immer es hinzeigt, was noch zu definieren ist.

board_init() in board_init() in der Funktion board_init() , wo zprintf eine eindeutige Funktion zugewiesen wird (deren Funktionssignatur übereinstimmt), abhängig von der Version, die in version.h definiert ist

zprintf = &printf; zprintf ruft printf zum Debuggen auf

oder

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

Das Ausführen des Codes sieht folgendermaßen 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 im Debug-Modus printf oder im Release-Modus nichts tun. Dies ist viel einfacher, als das gesamte Projekt durchzugehen und Code zu kommentieren oder zu löschen. Alles, was ich tun muss, ist die Version in version.h ändern und der Code wird den Rest erledigen!


Der Funktionszeiger wird normalerweise von typedef definiert und als Parameter und Rückgabewert verwendet.

Obenstehende Antworten haben schon 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;
}

Der Leitfaden zum gefeuert werden: Wie man Funktionszeiger in GCC auf x86-Maschinen missbrauchen, indem Sie Ihren Code von Hand kompilieren:

  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 Tauschfunktion

    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-Loop-Zähler an 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 zu 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);
    

Eine meiner Lieblingsverwendungen für Funktionszeiger ist, wie billig und einfach 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 sind in vielen Situationen nützlich, zB:

  • COM-Objekte Mitglieder sind Zeiger auf Funktion ag: This->lpVtbl->AddRef(This); AddRef ist ein Zeiger auf eine Funktion.
  • Funktions-Callback, zum Beispiel eine benutzerdefinierte Funktion, um zwei Variablen zu vergleichen, die als Callback an eine spezielle Sortierfunktion übergeben werden sollen.
  • sehr nützlich für Plugin-Implementierung und Application SDK.

Ich habe einmal an einem Datenerfassungssystem gearbeitet und jedes Feld hatte seine eigenen Validierungs- und Fehlermeldungen. Diese wurden in Tabellen mit #definierten Namen gespeichert. Es war wirklich einfach, neue Felder mit benutzerdefinierten Verarbeitungs- und benutzerdefinierten Fehlermeldungen hinzuzufügen, oft wurde nacheinander eine Reihe von Validierungen durchgeführt. Es war eine Art Überladen von Methoden.

definiere PHONE_VAL 42

Definieren Sie PHONE_ERROR 1012

...

Validierungen [PHONE_VAL] = & phone_validation_ftn ();

...

Fehler [PHONE_ERROR] = & phone_not_valid ();


Von Grund auf neu Funktion hat einige Speicheradresse von wo sie starten. In der Assembler-Sprache werden sie als aufgerufen (Aufruf der Speicheradresse der Funktion). Kommen wir nun zu C zurück. Wenn die Funktion eine Speicheradresse hat, kann sie durch Zeiger in C. durch die Regeln von C manipuliert werden

1. Zuerst müssen Sie einen Zeiger zur Funktion deklarieren. 2.Passieren Sie die Adresse der gewünschten Funktion

**** 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");
}

Danach sieht man, wie die Maschine sie versteht. Einblick in den Maschinenbefehl des obigen Programms in 32-Bit-Architektur.

Der rote Markierungsbereich zeigt an, wie die Adresse ausgetauscht und in eax gespeichert wird. Dann ist dies eine Aufrufanweisung auf eax. eax enthält die gewünschte Adresse der Funktion







function-pointers