1506 Stimmen

Wie funktionieren Funktionszeiger in C?

Ich habe in letzter Zeit einige Erfahrungen mit Funktionszeigern in C gemacht.

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

43 Stimmen

Auch: Eine etwas ausführlichere Analyse von C-Zeigern finden Sie unter blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge . Auch, Programmierung von Grund auf zeigt, wie sie auf Maschinenebene funktionieren. Verstehen Das "Speichermodell" von C ist sehr nützlich, um zu verstehen, wie C-Zeiger funktionieren.

10 Stimmen

Tolle Infos. Nach dem Titel zu urteilen, hätte ich eigentlich eine Erklärung erwartet, wie "Funktionszeiger funktionieren", nicht wie sie codiert sind :)

2 Stimmen

Die folgende Antwort ist kürzer und sehr viel leichter zu verstehen: stackoverflow.com/a/142809/2188550

26voto

Zack Sheffield Punkte 83

Eine weitere gute Verwendung von Funktionszeigern:
Problemloser Wechsel zwischen Versionen

Sie sind sehr praktisch, wenn Sie verschiedene Funktionen zu verschiedenen Zeiten oder in verschiedenen Entwicklungsphasen benötigen. Ich entwickle beispielsweise eine Anwendung auf einem Host-Computer, der über eine Konsole verfügt, aber die endgültige Version der Software wird auf einem Avnet ZedBoard installiert (das über Anschlüsse für Displays und Konsolen verfügt, die aber für die endgültige Version nicht benötigt werden). Während der Entwicklung werde ich also Folgendes verwenden printf um Status- und Fehlermeldungen anzuzeigen, aber wenn ich fertig bin, möchte ich nichts ausdrucken. Ich habe Folgendes 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

Sur version.c Ich werde 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 im Prototyp version.h comme

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

Wenn es in der Anwendung referenziert wird, beginnt es mit der Ausführung an dem Ort, auf den es zeigt, was noch zu definieren ist.

Sur version.c , Hinweis im board_init() Funktion, bei der zprintf wird eine eindeutige Funktion zugewiesen (deren Funktionssignatur übereinstimmt), je nach der Version, die in version.h

zprintf = &printf; zprintf ruft printf zu Debugging-Zwecken auf

o

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

Wenn der Code ausgeführt wird, sieht er wie folgt 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 verwendet printf wenn Sie sich im Debug-Modus befinden, oder nichts tun, wenn Sie sich im Release-Modus befinden. Das 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 und der Code erledigt den Rest!

7 Stimmen

Sie werden viel Leistungszeit verlieren. Stattdessen könnten Sie ein Makro verwenden, das einen Codeabschnitt auf der Grundlage von Debug / Release aktiviert und deaktiviert.

25voto

Eric Punkte 18690

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

In den obigen Antworten wurde 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;
}

21voto

Mohit Dabas Punkte 2283

Eine Funktion, die bei Null anfängt, hat eine Speicheradresse, von der aus sie mit der Ausführung beginnt. In der Assembler-Sprache werden sie als (call "function's memory address") genannt.Jetzt zurück zu C Wenn Funktion eine Speicheradresse hat, dann können sie durch Zeiger in C manipuliert werden.

1. zuerst müssen Sie einen Zeiger auf eine Funktion deklarieren 2. die Adresse der gewünschten Funktion übergeben

****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 here Danach können wir sehen, wie die Maschine sie versteht. Ein Blick auf die Maschinenbefehle des obigen Programms in der 32-Bit-Architektur.

Der rot markierte Bereich zeigt, wie die Adresse ausgetauscht und in eax gespeichert wird. Dann folgt eine Aufrufanweisung auf eax. eax enthält die gewünschte Adresse der Funktion.

0 Stimmen

Wie verwende ich einen Funktionszeiger, der von einer Methode zurückgegeben wird? something() scheint das Programm einfach abzustürzen. Ich habe einige Kontext und fehlgeschlagenen Code hier: stackoverflow.com/questions/67152106

19voto

Vamsi Punkte 51

Einer der wichtigsten Verwendungszwecke von Funktionszeigern in C ist der Aufruf einer zur Laufzeit ausgewählten Funktion. Die C-Laufzeitbibliothek hat zum Beispiel zwei Routinen, qsort y bsearch die einen Zeiger auf eine Funktion enthalten, die aufgerufen wird, um zwei zu sortierende Elemente zu vergleichen; so können Sie alles nach beliebigen Kriterien sortieren bzw. suchen.

Ein sehr einfaches Beispiel: Wenn es eine Funktion namens print(int x, int y) die ihrerseits den Aufruf einer Funktion erfordern kann (entweder add() o sub() die vom gleichen Typ sind), dann fügen wir ein Funktionszeiger-Argument zu der print() Funktion wie unten dargestellt:

#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

19voto

Richard Chambers Punkte 15587

Ein Funktionszeiger ist eine Variable, die die Adresse einer Funktion enthält. Da es sich um eine Zeigervariable handelt, die allerdings einige eingeschränkte Eigenschaften aufweist, können Sie sie so gut wie jede andere Zeigervariable in Datenstrukturen verwenden.

Die einzige Ausnahme, die mir einfällt, ist die Behandlung des Funktionszeigers als Verweis auf etwas anderes als einen einzelnen Wert. Zeigerarithmetik durch Inkrementieren oder Dekrementieren eines Funktionszeigers oder Addieren/Subtrahieren eines Offsets zu einem Funktionszeiger ist nicht wirklich von Nutzen, da ein Funktionszeiger nur auf eine einzige Sache zeigt, nämlich den Einstiegspunkt einer Funktion.

Die Größe einer Funktionszeigervariable, d.h. die Anzahl der von der Variablen belegten Bytes, kann je nach zugrunde liegender Architektur variieren, z.B. x32 oder x64 oder was auch immer.

Die Deklaration für eine Funktionszeigervariable muss dieselbe Art von Informationen enthalten wie eine Funktionsdeklaration, damit der C-Compiler die üblichen Prüfungen durchführen kann. Wenn Sie in der Deklaration/Definition des Funktionszeigers keine Parameterliste angeben, ist der C-Compiler nicht in der Lage, die Verwendung von Parametern zu überprüfen. Es gibt Fälle, in denen diese fehlende Überprüfung nützlich sein kann, aber denken Sie 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 eine int und eine char * und gibt eine int
  • pFunc ist ein Funktionszeiger, dem die Adresse einer Funktion zugeordnet ist, die eine int und eine char * und gibt eine int

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

Beachten Sie die Syntax, die bei der Deklaration/Definition eines Funktionszeigers verwendet wird, bei der Klammern verwendet werden, um die natürlichen Vorrangregeln für Operatoren zu umgehen.

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

Sie können Parameterlisten mit variabler Länge in der Definition eines Funktionszeigers 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, aber es eliminiert die Möglichkeit für den C-Compiler, die angegebene Argumentliste zu überprüfen.

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-Stil Besetzungen

Sie können Casts im Stil von C mit Funktionszeigern verwenden. Seien Sie sich jedoch bewusst, dass ein C-Compiler bei Überprüfungen nachlässig sein oder eher Warnungen als Fehler ausgeben kann.

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 auf Gleichheit vergleichen

Sie können überprüfen, ob ein Funktionszeiger gleich einer bestimmten Funktionsadresse ist, indem Sie eine if Anweisung, obwohl ich nicht sicher bin, wie nützlich das sein würde. 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 wollen, von denen jedes Element Unterschiede in der Argumentliste hat, dann können Sie einen Funktionszeiger mit unspezifizierter Argumentliste definieren (nicht void was bedeutet, dass es keine Argumente gibt, sondern nur nicht spezifizierte), etwa wie folgt, obwohl Sie möglicherweise Warnungen vom C-Compiler erhalten. Dies funktioniert auch für einen Funktionszeiger-Parameter für 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 Verwendung von Global struct mit Funktionszeigern

Sie können die static Schlüsselwort, um eine Funktion zu spezifizieren, deren Name ein Dateibereich ist, und diese dann einer globalen Variablen zuzuweisen, um so etwas Ähnliches wie die namespace Funktionalität von C++.

Definieren Sie in einer Header-Datei eine Struktur, die unser Namensraum sein wird, zusammen mit einer globalen Variablen, die ihn 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 werden. Die const Modifikator 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 tun wie die C-Stil namespace Ansatz, bei dem eine bestimmte Bibliotheksschnittstelle von einer Fabrikmethode in einer Bibliotheksschnittstelle angefordert wird, die die Erstellung einer struct mit Funktionszeigern.. Diese Bibliotheksschnittstelle lädt die angeforderte DLL-Version, erstellt eine Struktur mit den erforderlichen Funktionszeigern und gibt die Struktur dann an den anfordernden Aufrufer zur Verwendung 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 verwendet werden:

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, der 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. Auf diese Weise kann eine abstrakte Hardwareschicht bereitgestellt werden, die von Software verwendet wird, die eine Fabrikfunktion aufruft, um die spezifische Hardware-Funktionsschnittstelle zu erhalten, und dann die bereitgestellten Funktionszeiger verwendet, um Aktionen für die zugrunde liegende Hardware durchzuführen, ohne Implementierungsdetails über das spezifische Ziel kennen zu müssen.

Funktionszeiger zum Erstellen von Delegaten, Bearbeitern und Rückrufen

Sie können Funktionszeiger verwenden, um eine Aufgabe oder Funktionalität zu delegieren. Das klassische Beispiel in C ist der Funktionszeiger für Vergleichsdelegierungen, der mit den Funktionen der Standard-C-Bibliothek verwendet wird qsort() y bsearch() um die Sortierreihenfolge für die Sortierung einer Liste von Elementen oder die Durchführung einer binären Suche über eine sortierte Liste von Elementen anzugeben. Der Delegat der Vergleichsfunktion gibt den Sortieralgorithmus an, der bei der Sortierung oder der binären Suche verwendet wird.

Eine andere Verwendung ähnelt der Anwendung eines Algorithmus auf einen Container der C++ Standard Template Library.

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 Message-Maps verwendet etwas Ähnliches, um Windows-Nachrichten zu behandeln, die an ein Fenster oder einen Thread geliefert werden.

Asynchrone Funktionen, die einen Rückruf erfordern, ähneln einem Event-Handler. Der Benutzer der asynchronen Funktion ruft die asynchrone Funktion auf, um eine Aktion zu starten, und übergibt einen Funktionszeiger, den die asynchrone Funktion aufruft, sobald die Aktion abgeschlossen ist. In diesem Fall ist das Ereignis die Beendigung der Aufgabe der asynchronen Funktion.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X