Jak działają Wskaźniki funkcji w C?

Miałem ostatnio pewne doświadczenie ze wskaźnikami funkcyjnymi w C.

Kontynuując tradycję odpowiadania na własne pytania, postanowiłem zrobić małe podsumowanie samych podstaw, dla tych, którzy potrzebują szybkiego zanurzenia się w temacie.

Author: Yuval Adam , 2009-05-08

12 answers

Wskaźniki funkcji w C

Zacznijmy od podstawowej funkcji, którą będziemy wskazywać na :

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

Po pierwsze, zdefiniujmy wskaźnik do funkcji, która otrzymuje 2 int s i zwraca int:

int (*functionPtr)(int,int);

Teraz możemy bezpiecznie wskazać naszą funkcję:

functionPtr = &addInt;

Teraz, gdy mamy wskaźnik do funkcji, użyjmy jej:

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

Przekazanie wskaźnika do innej funkcji jest w zasadzie takie samo:

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

Możemy użyć funkcji wskaźniki w wartościach zwrotnych również (spróbuj nadążyć, robi się bałagan):

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

Ale o wiele ładniej jest użyć 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;
}
 1264
Author: Yuval Adam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-29 22:23:52

W języku C można używać wskaźników funkcyjnych do programowania obiektowego w C.

Na przykład następujące wiersze są zapisywane w języku C:

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

Tak, -> i brak operatora new jest martwym rozdaniem, ale z pewnością wydaje się sugerować, że ustawiamy tekst jakiejś klasy String na "hello".

Używając wskaźników funkcji, można emulować metody w C .

Jak to się robi?

The String klasa jest w rzeczywistości struct z kilkoma wskaźnikami funkcji, które działają jako sposób na symulację metod. Poniżej znajduje się deklaracja częściowa klasy 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();

Jak widać, metody klasy String są w rzeczywistości wskaźnikami funkcji do zadeklarowanej funkcji. Podczas przygotowywania instancji String, funkcja newString jest wywoływana w celu skonfigurowania wskaźników funkcji do ich odpowiednich funkcji:

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

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

    self->set(self, "");

    return self;
}

Na przykład, getString funkcja, która nazywa wywołując metodę get definiuje się następująco:

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

Jedną rzeczą, którą można zauważyć, jest to, że nie ma koncepcji instancji obiektu i nie ma metod, które są rzeczywiście częścią obiektu, więc "obiekt jaźni" musi być przekazywany przy każdym wywołaniu. (A internal jest tylko ukrytym struct, który został wcześniej pominięty w liście kodu-jest to sposób ukrywania informacji, ale nie ma to znaczenia dla wskaźników funkcji.)

Więc, zamiast aby wykonać s1->set("hello");, należy przejść w obiekcie, aby wykonać akcję na s1->set(s1, "hello").

Z tym drobnym wyjaśnieniem, które musi przejść w odniesieniu do siebie z drogi, przejdziemy do następnej części, która jest dziedziczenie w C .

Powiedzmy, że chcemy utworzyć podklasę String, powiedzmy ImmutableString. Aby ciąg stał się niezmienny, metoda set nie będzie dostępna, zachowując dostęp do get i length i zmuszając "konstruktor" do zaakceptowania 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);

Zasadniczo, dla wszystkich podklas, dostępne metody są ponownie wskaźnikami funkcji. Tym razem deklaracja dla metody set nie jest obecna, dlatego nie może być wywołana w ImmutableString.

Jeśli chodzi o implementację ImmutableString, jedynym odpowiednim kodem jest funkcja "konstruktor", 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;
}

W tworzeniu instancji ImmutableString, Wskaźniki funkcji do metod get i length odnoszą się do metody String.get i String.length, przez przechodząc przez zmienną base, która jest wewnętrznie przechowywanym obiektem String.

Użycie wskaźnika funkcji może osiągnąć dziedziczenie metody z klasy nadrzędnej.

Możemy dalej kontynuować polimorfizm w C .

Jeśli na przykład chcemy zmienić zachowanie metody length, aby zwracała 0 cały czas w klasie ImmutableString, wszystko, co musiałoby być zrobione, to:

  1. Dodaj funkcję, która będzie służyć jako metoda nadrzędna length.
  2. przejdź do "konstruktora" i ustaw wskaźnik funkcji na nadrzędną metodę length.

Dodanie nadrzędnej metody length w ImmutableString może być wykonane przez dodanie lengthOverrideMethod:

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

Następnie wskaźnik funkcji dla metody length w konstruktorze jest podpięty do 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;
}

Teraz zamiast identycznego zachowania metody length w klasie ImmutableString jako klasy String, Teraz metoda length będzie odnosić się do zachowania zdefiniowanego w funkcji lengthOverrideMethod.

Muszę dodać zastrzeżenie, że nadal uczę się pisać w stylu programowania obiektowego w C, więc prawdopodobnie są punkty, które nie wyjaśniłem dobrze, lub mogą być po prostu nieznaczące w kategoriach jak najlepiej zaimplementować OOP w C. Ale moim celem było zilustrowanie jednego z wielu zastosowań wskaźników funkcji.

Aby uzyskać więcej informacji na temat wykonywania programowania obiektowego w języku C, zapoznaj się z następujące pytania:

 270
Author: coobird,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:30

[16]}the guide to getting fired: how to abuse function pointers in GCC on x86 machines by compiling your code by hand:

Te literały łańcuchów są bajtami 32-bitowego kodu maszynowego x86. 0xC3 jest instrukcją x86 ret .

Zwykle nie piszesz ich ręcznie, piszesz w języku asemblera, a następnie używasz asemblera podobnego do nasm, aby złożyć go w płaski plik binarny, który zrzut heksowy do literału C.

  1. Zwraca bieżącą wartość w rejestrze EAX

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. Napisz funkcję swap

    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. Zapisuje licznik pętli for do 1000, wywołując za każdym razem jakąś funkcję

    ((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. Można nawet napisać funkcję rekurencyjną, która liczy się do 100

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

Należy zauważyć, że Kompilatory umieszczają literały łańcuchów w sekcji .rodata (lub .rdata w systemie Windows), która jest połączona jako część segmentu tekstu (wraz z kodem funkcji).

Segment tekstowy ma Read + Exec permission, więc rzucanie literałów ciągów znaków do wskaźników funkcyjnych działa bez potrzeby wywołania systemowego mprotect() lub VirtualProtect(), tak jak potrzebowałbyś dynamicznie przydzielanej pamięci. (Lub gcc -z execstack łączy program ze stosem + segment danych + plik wykonywalny sterty, jako szybki hack.)


Aby je zdemontować, możesz skompilować to, aby umieścić etykietę na bajtach i użyć disassemblera.

// 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";

Kompilując z gcc -c -m32 foo.c i demontując z objdump -D -rwC -Mintel, możemy uzyskać montaż i dowiedzieć się, że kod ten narusza ABI poprzez zatrzaskiwanie EBX (rejestru zachowanego przez wywołania) i jest ogólnie nieefektywny.

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

Ten kod maszynowy będzie (prawdopodobnie) działał w 32-bitowym kodzie w systemach Windows, Linux, OS X i tak dalej: domyślne konwencje wywołujące na wszystkich tych systemach przesyłają args na stosie zamiast wydajniej w rejestrach. Ale EBX jest zachowany we wszystkich normalnych konwencjach wywołania, więc używanie go jako rejestru scratch bez zapisywania/przywracania może łatwo spowodować awarię rozmówcy.

 191
Author: Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-09 13:58:10

Jednym z moich ulubionych zastosowań dla wskaźników funkcji jest tanie i łatwe Iteratory -

#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);
}
 97
Author: Nick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-24 16:40:57

Wskaźniki funkcji stają się łatwe do zadeklarowania, gdy masz podstawowe deklaratory:

  • id: ID: ID to
  • Pointer: *D: d wskaźnik do
  • funkcja: D(<parameters>): d funkcja przyjmująca <parametry> zwracająca

Podczas gdy D to kolejny deklarator zbudowany przy użyciu tych samych reguł. W końcu gdzieś kończy się na ID (zobacz przykład poniżej), która jest nazwą deklarowanego podmiotu. Spróbujmy zbudować funkcja biorąc wskaźnik do funkcji biorąc nic i zwracając int, i zwracając wskaźnik do funkcji biorąc znak i zwracając int. Z type-defs jest tak:

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

Jak widzisz, dość łatwo jest go zbudować za pomocą typedefów. Bez typedefów też nie jest trudno z powyższymi regułami deklaratoryjnymi, stosowanymi konsekwentnie. Jak widzisz przegapiłem część, na którą wskazuje wskaźnik, i rzecz, którą zwraca funkcja. To właśnie pojawia się po lewej stronie deklaracja, i nie jest interesująca: jest dodawana na końcu, jeśli ktoś już zbudował deklarator. Zróbmy to. W tym samym roku, w 2011 roku, w ramach projektu "budowa nowego budynku", rozpoczęto prace nad budową nowego budynku.]}

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

Jak widzisz, można opisać Typ całkowicie dodając deklaratory jeden po drugim. Budowę można wykonać na dwa sposoby. Jeden jest oddolny, zaczynając od tego, co właściwe (liście) i przechodząc do identyfikatora. W drugą stronę odgórnie, zaczynając od identyfikatora, przechodząc w dół do liści. Pokażę w obie strony.

Do Dołu

Budowa zaczyna się od rzeczy po prawej: rzeczy zwróconej, czyli funkcji przyjmującej znak. Aby zachować odrębność zgłaszających, będę ich numerował:

D1(char);

Wstawiłem parametr char bezpośrednio, ponieważ jest trywialny. Dodanie wskaźnika do deklaratora przez zastąpienie D1 przez *D2. Należy pamiętać, że nawiasy należy owijać wokół *D2. Które można poznać poprzez sprawdzanie pierwszeństwa *-operator i operatora wywołania funkcji (). Bez nawiasów kompilator odczytałby je jako *(D2(char p)). Ale to nie byłoby zwykłe zastąpienie D1 przez *D2 już, oczywiście. Nawiasy są zawsze dozwolone wokół zgłaszających. Więc nie zrobisz nic złego, jeśli dodasz ich za dużo.

(*D2)(char);

Typ powrotu jest zakończony! Teraz zastąp {[28] } przez deklarator funkcji funkcja przyjmująca <parameters> zwracająca , czyli D3(<parameters>) w której jesteśmy teraz.

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

Zauważ, że nie są potrzebne żadne nawiasy, ponieważ chcemy D3 być tym razem deklaratorką funkcji, a nie deklaratorką wskaźnika. Świetnie, jedyne co pozostało to parametry. Parametr jest wykonywany dokładnie tak samo jak typ zwracany, tylko char zastąpiony przez void. Więc skopiuję:

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

Zamieniłem D2 na ID1, ponieważ skończyliśmy z tym parametrem (jest to już wskaźnik do funkcji-nie ma potrzeby inny zgłaszający). ID1 będzie nazwą parametru. Powiedziałem wyżej na końcu, że dodaje się typ, który modyfikują wszyscy deklaratoryści-ten, który pojawia się po lewej stronie każdej deklaracji. Dla funkcji, która staje się typem return. Dla pointerów wskazuje na typ itd... Ciekawe, po zapisaniu typu, pojawi się w odwrotnej kolejności, po prawej stronie :) w każdym razie, zastąpienie go daje pełną deklarację. Oba razy int oczywiście.

int (*ID0(int (*ID1)(void)))(char)
W tym przykładzie wywołałem identyfikator funkcji ID0.

Top Down

Zaczyna się od identyfikatora po lewej stronie w opisie typu, owijając ten deklarator, gdy przechodzimy przez prawo. Zacznij od funkcji przyjmującej <parametry> zwracającej

ID0(<parameters>)

Następną rzeczą w opisie (po "powrocie") był wskaźnik do . Załóżmy to:

*ID0(<parameters>)

Potem następny rzecz była funkton biorąc <parametry > zwracając . Parametr jest prostym znakiem, więc wstawiamy go od razu, ponieważ jest naprawdę trywialny.

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

Zwróć uwagę na nawiasy, które dodaliśmy, ponieważ ponownie chcemy, aby * wiązały się najpierw, a potem (char). W przeciwnym razie odczytałby funkcję przyjmującą <parametry > zwracającą funkcję .... Noes, funkcje zwracające funkcje nie są nawet dozwolone.

Teraz musimy tylko umieścić <parametry>. Pokażę krótką wersję deriveration, ponieważ myślę, że już masz pomysł, jak to zrobić.

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

Po prostu postaw int przed deklaracjonistami, jak to zrobiliśmy z oddolnym,i jesteśmy skończeni

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

The nice thing

Lepiej oddolnie czy odgórnie? Jestem przyzwyczajony do oddolnego,ale niektórzy mogą czuć się bardziej komfortowo z odgórnym. To chyba kwestia gustu. Nawiasem mówiąc, jeśli zastosujesz wszystkie operatory w tej deklaracji, w końcu dostaniesz int:
int v = (*ID0(some_function_pointer))(some_char);

Jest to dobra właściwość deklaracji w C: deklaracja stwierdza, że jeśli operatory te są używane w wyrażeniu wykorzystującym IDENTYFIKATOR, to daje Typ po lewej stronie. Tak samo jest z tablicami.

Mam nadzieję, że spodobał ci się ten mały tutorial! Teraz możemy połączyć się z tym, gdy ludzie zastanawiają się nad dziwną składnią deklaracji funkcji. Starałem się umieścić jak najmniej C wewnętrzne, jak to możliwe. Możesz edytować/naprawiać rzeczy w nim.

 23
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-09 13:38:17

Inne dobre zastosowanie dla wskaźników funkcji:
przełączanie między wersjami bezboleśnie

Są bardzo przydatne, gdy potrzebujesz różnych funkcji w różnym czasie lub na różnych etapach rozwoju. Na przykład, rozwijam aplikację na komputerze hosta, który ma konsolę, ale ostateczne wydanie oprogramowania zostanie umieszczone na płycie Avnet ZedBoard (która ma porty dla wyświetlaczy i konsol, ale nie są one potrzebne / poszukiwane w ostatecznej wersji). Więc podczas rozwój, użyję printf, aby wyświetlić komunikaty o statusie i błędach, ale kiedy skończę, nie chcę niczego drukować. Oto co zrobiłem:

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

W version.c zdefiniuję 2 prototypy funkcji obecne w version.h

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

Zwróć uwagę, jak wskaźnik funkcji jest prototypowany w version.h jako

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

Gdy jest odwołany w aplikacji, rozpocznie wykonywanie wszędzie tam, gdzie jest wskazywany, co jeszcze do zdefiniowania.

W version.c zwróć uwagę w funkcji board_init()gdzie zprintf przypisana jest unikalna funkcja (której sygnatura funkcji pasuje) w zależności od wersji zdefiniowanej w version.h

zprintf = &printf; zprintf wywołuje printf do celów debugowania

Lub

zprintf = &noprint; zprintf po prostu zwraca i nie uruchomi niepotrzebnego kodu

Uruchomienie kodu będzie wyglądało tak:

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

Powyższy kod użyje printf jeśli w trybie debugowania, lub do nic, jeśli w trybie zwolnienia. Jest to o wiele łatwiejsze niż przeglądanie całego projektu i komentowanie lub usuwanie kodu. Wszystko, co muszę zrobić, to zmienić wersję w version.h, A kod zrobi resztę!

 21
Author: Zack Sheffield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-11 15:36:22

Wskaźnik funkcji jest zwykle zdefiniowany przez typedef i używany jako wartość param & return,

Powyższe odpowiedzi już wiele wyjaśniły, podaję tylko Pełny przykład:

#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;
}
 13
Author: Eric Wang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-10 08:50:57

Jednym z dużych zastosowań wskaźników funkcji w C jest wywołanie funkcji wybranej w czasie wykonywania. Na przykład, biblioteka C run-time ma dwie procedury, qsort i bsearch, które pobierają wskaźnik do funkcji, która jest wywoływana w celu porównania dwóch sortowanych elementów; pozwala to na sortowanie lub wyszukiwanie, odpowiednio, cokolwiek, na podstawie dowolnych kryteriów, których chcesz użyć.

Bardzo prosty przykład, jeśli istnieje jedna funkcja o nazwie print (int x, int y), która z kolei może wymagać wywołania funkcji add () lub sub() które są podobnych typów wtedy, co zrobimy, dodamy jeden argument wskaźnika funkcji do funkcji print (), jak pokazano poniżej:-

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

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

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

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

    return 0;
}
 9
Author: Vamsi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-03 17:52:29

Uruchamianie funkcji od podstaw ma pewien adres pamięci, z którego zaczyna się uruchamiać. W języku asemblera są one nazywane jako (call "adres pamięci funkcji").Teraz wróć do C Jeśli funkcja ma adres pamięci to mogą być manipulowane wskaźnikami w C.So według zasad C

1.Najpierw musisz zadeklarować wskaźnik do funkcji 2.Podaj adres żądanej funkcji

* * * * Uwaga - > funkcje powinny być tego samego typu * * * *

Ten prosty program będzie Zilustruj Wszystko.

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

Tutaj wpisz opis obrazkaPo tym pozwala zobaczyć, jak maszyna je rozumie.Rzut oka na instrukcję maszynową powyższego programu w architekturze 32 bitowej.

Obszar czerwonego znaku pokazuje, w jaki sposób adres jest wymieniany i przechowywany w eax.Następnie jest to Instrukcja wywołania na eax. eax zawiera żądany adres funkcji

 3
Author: Mohit Dabas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-26 08:09:51

Wskaźnik funkcji jest zmienną zawierającą adres funkcji. Ponieważ jest to zmienna wskaźnika, ale z pewnymi zastrzeżonymi właściwościami, możesz jej używać prawie tak, jak każdej innej zmiennej wskaźnika w strukturach danych.

Jedynym wyjątkiem, o którym myślę, jest traktowanie wskaźnika funkcji jako wskazującego na coś innego niż pojedyncza wartość. Wykonywanie arytmetyki wskaźnika przez zwiększanie lub zmniejszanie wskaźnika funkcji lub dodawanie/odejmowanie przesunięcia do funkcji wskaźnik nie jest tak naprawdę użyteczny, ponieważ wskaźnik funkcji wskazuje tylko jedną rzecz, punkt wejścia funkcji.

Rozmiar zmiennej wskaźnika funkcji, liczba bajtów zajętych przez zmienną, może się różnić w zależności od podstawowej architektury, np. x32 lub x64 lub cokolwiek innego.

Deklaracja zmiennej wskaźnika funkcji musi określać ten sam rodzaj informacji, co deklaracja funkcji, aby kompilator C mógł wykonywać rodzaje kontroli, które jak zwykle. Jeśli nie podasz listy parametrów w deklaracji/definicji wskaźnika funkcji, kompilator C nie będzie w stanie sprawdzić użycia parametrów. Są przypadki, w których ten brak kontroli może być przydatny, jednak pamiętaj, że siatka bezpieczeństwa została usunięta.

Niektóre przykłady:

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.

Pierwsze dwie deklaracje są nieco podobne w tym:

  • func jest funkcją, która przyjmuje int i a char * i zwraca int
  • pFunc jest wskaźnikiem funkcji, do którego przypisany jest adres funkcji, która przyjmuje int i a char * i zwraca int

Więc z powyższego możemy mieć linię źródłową, w której adres funkcji func() jest przypisany do zmiennej wskaźnika funkcji pFunc jak w pFunc = func;.

Zwróć uwagę na składnię używaną z deklaracją/definicją wskaźnika funkcji, w której nawiasy są używane do przezwyciężenia pierwszeństwa operatora naturalnego Zasady.

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

Kilka Różnych Przykładów Użycia

Niektóre przykłady użycia wskaźnika funkcji:

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

Można użyć listy parametrów zmiennej długości w definicji wskaźnika funkcji.

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

Lub nie można w ogóle określić listy parametrów. Może to być użyteczne, ale eliminuje możliwość sprawdzania listy argumentów przez kompilator C.

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

Odlewy w stylu C

Możesz użyć C stylowe odlewy ze wskaźnikami funkcyjnymi. Należy jednak pamiętać, że kompilator C może być luźny w kwestii sprawdzania lub dostarczania ostrzeżeń, a nie błędów.

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.

Porównaj Wskaźnik funkcji do równości

Możesz sprawdzić, czy wskaźnik funkcji jest równy konkretnemu adresowi funkcji za pomocą instrukcji if, chociaż nie jestem pewien, jak przydatne byłoby to. Inne porównywarki wydają się mieć jeszcze mniejszą użyteczność.

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

Tablica funkcji Wskaźniki

I jeśli chcesz mieć tablicę wskaźników funkcji każdy z elementów, których lista argumentów ma różnice, możesz zdefiniować wskaźnik funkcji z listą argumentów nieokreślonych (Nie void co oznacza brak argumentów, ale tylko nieokreślone) coś takiego jak poniżej, chociaż możesz zobaczyć ostrzeżenia z kompilatora C. Działa to również dla parametru wskaźnika funkcji do funkcji:

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 Wskaźniki

Możesz użyć słowa kluczowego static, aby określić funkcję, której nazwa to file scope, a następnie przypisać ją do zmiennej globalnej jako sposób dostarczenia czegoś podobnego do namespace funkcjonalności C++.

W pliku nagłówkowym zdefiniuj strukturę, która będzie naszą przestrzenią nazw wraz ze zmienną globalną, która jej używa.

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;

Następnie w pliku źródłowym C:

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

Zostanie to użyte przez podanie pełnej nazwy zmiennej Global struct i nazwa użytkownika, aby uzyskać dostęp do funkcji. Modyfikator const jest używany w trybie globalnym, dzięki czemu nie można go zmienić przez przypadek.

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

Obszary zastosowania wskaźników funkcji

Komponent biblioteki DLL może zrobić coś podobnego do podejścia w stylu C namespace, w którym określony interfejs biblioteki jest wymagany z metody fabrycznej w interfejsie biblioteki, który obsługuje tworzenie struct zawierającego Wskaźniki funkcji.. Ten interfejs biblioteki ładuje żądana wersja DLL, tworzy strukturę z niezbędnymi wskaźnikami funkcji, a następnie zwraca strukturę do wywołującego do użycia.

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

A to może być użyte jak w:

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

To samo podejście może być użyte do zdefiniowania abstrakcyjnej warstwy sprzętowej dla kodu, który używa określonego modelu sprzętu bazowego. Wskaźniki funkcji są wypełnione funkcjami specyficznymi dla sprzętu przez fabrykę, aby zapewnić specyficzną dla sprzętu funkcjonalność, która implementuje funkcje określony w abstrakcyjnym modelu sprzętowym. Może to być użyte do zapewnienia abstrakcyjnej warstwy sprzętowej używanej przez oprogramowanie, które wywołuje funkcję fabryczną w celu uzyskania określonego interfejsu funkcji sprzętowych, a następnie używa wskaźników funkcji dostarczanych do wykonywania działań dla podstawowego sprzętu bez konieczności znajomości szczegółów implementacji na temat konkretnego celu.

Funkcje do tworzenia delegatów, programów obsługi i wywołań zwrotnych

Możesz używać wskaźników funkcji jako sposób na delegowanie jakiegoś zadania lub funkcji. Klasyczny przykład w C jest wskaźnikiem funkcji delegata porównywania używanym ze standardowymi funkcjami biblioteki C qsort() i bsearch(), aby zapewnić kolejność sortowania dla sortowania listy elementów lub wykonywania wyszukiwania binarnego nad posortowaną listą elementów. Funkcja porównywania delegate określa algorytm porównywania używany w sortowaniu lub wyszukiwaniu binarnym.

Inne zastosowanie jest podobne do zastosowania algorytmu do standardowej biblioteki szablonów C++ Pojemnik.

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

Innym przykładem jest kod źródłowy GUI, w którym funkcja obsługi określonego zdarzenia jest rejestrowana przez dostarczenie wskaźnika funkcji, który jest faktycznie wywoływany, gdy zdarzenie ma miejsce. Microsoft MFC framework z mapami komunikatów używa czegoś podobnego do obsługi wiadomości Windows, które są dostarczane do okna lub wątku.

Funkcje asynchroniczne, które wymagają wywołania zwrotnego, są podobne do obsługi zdarzeń. Użytkownik funkcji asynchronicznej wywołuje funkcja asynchroniczna do uruchomienia jakiejś akcji i zapewnia wskaźnik funkcji, który funkcja asynchroniczna wywoła po zakończeniu akcji. W tym przypadku zdarzenie jest funkcją asynchroniczną wykonującą swoje zadanie.

 2
Author: Richard Chambers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-20 05:47:15

Ponieważ wskaźniki funkcji są często typowanymi wywołaniami zwrotnymi, warto przyjrzeć się Wpisz bezpieczne wywołania zwrotne . To samo dotyczy punktów wejścia, itp. funkcji, które nie są wywołaniami zwrotnymi.

C jest dość kapryśna i jednocześnie wyrozumiała:)

 0
Author: Tim Post,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-09 13:56:12

Wskaźniki funkcji są przydatne w wielu sytuacjach, np:

  • obiekty COM są wskaźnikiem do funkcji ag: This->lpVtbl->AddRef(This); AddRef jest wskaźnikiem do funkcji.
  • funkcja callback, na przykład funkcja zdefiniowana przez użytkownika do porównywania dwie zmienne, które mają być przekazane jako wywołanie zwrotne do specjalnej funkcji sortowania.
  • bardzo przydatne do implementacji wtyczek i SDK aplikacji.
 -8
Author: milevyo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-02 13:15:05