Как работают указатели функций в C?

в последнее время у меня был некоторый опыт работы с указателями функций в C.

Итак, продолжая традицию отвечать на ваши собственные вопросы, я решил сделать небольшое резюме самых основ, для тех, кто нуждается в быстром погружении в тему.

12 ответов


указатели функций в C

давайте начнем с основной функции, которой мы будем указывая на:

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

во-первых, давайте определим указатель на функцию, которая получает 2 ints и возвращает int:

int (*functionPtr)(int,int);

теперь мы можем смело указать на нашу функцию:

functionPtr = &addInt;

теперь у нас есть указатель на функцию, давайте использовать:

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

передача указателя на другую функцию в основном то же самое:

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

мы также можем использовать указатели функций в возвращаемых значениях (старайтесь не отставать, это становится грязным):

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

но гораздо приятнее использовать 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;
}

указатели функций в C могут использоваться для выполнения объектно-ориентированного программирования в C.

например, следующие строки написаны на C:

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

да -> и отсутствии new оператор-мертвый, но это, похоже, означает, что мы устанавливаем текст некоторых String класс "hello".

с помощью указателей на функции, можно эмулировать методы в C.

как это достигается?

на String класс-это на самом деле struct с кучей указателей функций, которые действуют как способ моделирования методов. Ниже приводится частичное объявление 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();

как видно, методы String class фактически являются указателями функций на объявленную функцию. При подготовке экземпляра String, the newString функция вызывается для настройки указатели функций на их соответствующие функции:

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

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

    self->set(self, "");

    return self;
}

например,getString функция, которая вызывается вызова get метод определяется следующим образом:

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

одна вещь, которую можно заметить, заключается в том, что нет понятия экземпляра объекта и методов, которые на самом деле являются частью объекта, поэтому "самообъект" должен передаваться при каждом вызове. (И internal - это просто скрытый struct, который был исключен из кодекса листинг ранее - это способ выполнения скрытия информации, но это не относится к указателям функций.)

Итак, вместо того, чтобы сделать s1->set("hello");, необходимо пройти в объект, чтобы выполнить действие на s1->set(s1, "hello").

с этим незначительным объяснением, которое должно пройти в ссылке на себя, мы перейдем к следующей части, которая наследование в C.

предположим, мы хотим сделать подкласс String, сказать ImmutableString. Чтобы сделать строку неизменяемой,set метод не будет доступен, сохраняя при этом доступ к get и length, и заставить "конструктор" принять 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);

в основном, для всех подклассов доступные методы снова являются указателями функций. На этот раз декларация для set метода нет, поэтому его нельзя вызвать в ImmutableString.

что касается реализации ImmutableString, в только релевантным кодом является функция "конструктор",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;
}

при создании ImmutableString, функция указывает на get и length методы на самом деле относятся к String.get и String.length способом, путем base переменная, которая является внутренне сохраненной


руководство по увольнению: как злоупотреблять указателями функций в GCC на машинах x86, компилируя свой код вручную:

эти строковые литералы представляют собой байты 32-разрядного машинного кода x86. 0xC3 is на x86 ret - инструкции.

обычно вы не пишете их вручную, вы пишете на языке ассемблера, а затем используете ассемблер, такой как nasm чтобы собрать его в плоский двоичный файл, который вы hexdump в строку C буквальный.

  1. возвращает текущее значение в регистре EAX

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. написать функцию 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. напишите счетчик for-loop до 1000, вызывая некоторую функцию каждый раз

    ((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. вы даже можете написать рекурсивную функцию, которая считает до 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);
    

обратите внимание, что компиляторы помещают строковые литералы в (или .rdata в Windows), который связан как часть текстового сегмента (вместе с кодом для функции).

текстовый сегмент имеет разрешение Read+Exec, поэтому литералы строк для указателей функций работают без необходимости mprotect() или VirtualProtect() системные вызовы, как вам нужно для динамически выделенной памяти. (Или gcc -z execstack связывает программу со стеком + сегментом данных + исполняемым файлом кучи, как быстрый взлом.)


чтобы разобрать их, вы можете скомпилировать это, чтобы положить пометьте байты и используйте дизассемблер.

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

компиляция с gcc -c -m32 foo.c и демонтировать с objdump -D -rwC -Mintel, мы можем получить сборку и узнать, что этот код нарушает ABI путем clobbering EBX (регистр с сохраненным вызовом) и, как правило, неэффективен.

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

этот машинный код (вероятно) будет работать в 32-битном коде в Windows, Linux, OS X и т. д.: соглашения о вызовах по умолчанию для всех этих ОС передают args в стеке вместо более эффективного в регистре. Но EBX сохраняется во всех обычных соглашениях о вызовах, поэтому использование его в качестве регистра царапин без сохранения/восстановления может легко привести к сбою вызывающего абонента.


одно из моих любимых применений для указателей функций-это дешевые и простые итераторы -

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

указатели функций становятся легко объявить, как только у вас есть основные деклараторы:

  • id:ID: ID-это
  • указатели: *D: D указатель на
  • функция: D(<parameters>): функции D с <параметры> возвращение

в то время как D-Еще один Декларатор, построенный с использованием тех же правил. В конце концов, где-то, это заканчивается ID (см. ниже пример), который является именем объявленной сущности. Попробуем построить функцию, берущую указатель на функцию, ничего не берущую и возвращающую int, и возвращающую указатель на функцию, берущую char и возвращающую int. С Type-defs это так

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

как вы видите, его довольно легко создать с помощью typedefs. Без typedefs это не сложно и с вышеупомянутыми правилами Декларатора, применяемыми последовательно. Как вы видите, я пропустил часть, на которую указывает указатель, и что возвращает функция. Это то, что появляется в самой левой части декларации и не представляет интереса: оно добавляется в конце, если уже создан Декларатор. Давайте сделаем это. Построение его последовательно, первый wordy-показ структуры с использованием [ и ]:

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

как вы видите, можно полностью описать тип, добавляя деклараторы один за другим. Строительство может осуществляться двумя способами. Один снизу вверх, начиная с очень правильно (уходит) и прорабатывает путь до идентификатора. Другой способ-сверху вниз, начиная с идентификатора, работая вниз до листьев. Я покажу обе стороны.

Снизу Вверх

строительство начинается с вещи справа: вещь возвращается, которая является функцией, принимающей char. Чтобы деклараторы отличались друг от друга, я собираюсь пронумеровать их:

D1(char);

вставил параметр char напрямую, так как он тривиален. Добавление указателя на Декларатор путем замены D1 by *D2. Обратите внимание, что мы должны обернуть в скобки *D2. Это можно узнать, посмотрев на приоритет *-operator и оператор вызова функции (). Без наших скобок компилятор прочитал бы его как *(D2(char p)). Но это не будет простой заменой D1 на *D2 больше, конечно. Круглые скобки всегда разрешены вокруг деклараторов. Поэтому вы не сделаете ничего плохого, если добавите их слишком много, на самом деле.

(*D2)(char);

тип возврата полной! Теперь давайте заменим D2 декларатором функций


другое хорошее использование для указателей функций:
переключение между версиями безболезненно

они очень удобны в использовании, когда вам нужны разные функции в разное время или на разных этапах разработки. Например, я разрабатываю приложение на хост-компьютере с консолью, но окончательный выпуск программного обеспечения будет помещен на Avnet ZedBoard (который имеет порты для дисплеев и консолей, но они не нужны/не нужны для окончательного выпуска). Так во время развитие, я буду использовать printf для просмотра сообщений о состоянии и ошибках, но когда я закончу, я не хочу ничего печатать. Вот что я сделал:--20-->

версия.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

на version.c я определю 2 прототипа функций, присутствующих в version.h

версия.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;
}

обратите внимание, как прототипируется указатель функции в version.h as

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

когда на него ссылаются в приложении, он начнет выполняться везде, где он указывает, что еще не определено.

на version.c, что в


указатель функции обычно определяется typedef и используется как param & return value,

выше ответы уже многое объяснили, я просто приведу полный пример:

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

одним из больших применений указателей функций в C является вызов функции, выбранной во время выполнения. Например, библиотека времени выполнения C имеет две подпрограммы, qsort и bsearch, которые принимают указатель на функцию, вызываемую для сравнения двух сортируемых элементов; это позволяет сортировать или искать, соответственно, что-либо, на основе любых критериев, которые вы хотите использовать.

очень простой пример, если есть одна функция под названием print (int x, int y), которая, в свою очередь, может потребовать вызова функции add() или sub (), которые имеют аналогичные типы, то, что мы будем делать, мы добавим один аргумент указателя функции к функции print (), как показано ниже:-

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

начиная с нуля функция имеет некоторый адрес памяти, откуда они начинают выполняться. На языке ассемблера они называются как (вызовите "адрес памяти функции").Теперь вернемся к C, если функция имеет адрес памяти, то ими можно манипулировать указателями в C. Итак, по правилам c

1.Сначала нужно объявить указатель на функцию 2.Передайте адрес нужной функции

****Примечание->функции должны быть одного типа****

этот Простая программа проиллюстрирует все.

#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после этого давайте посмотрим, как машина понимает их.Проблеск инструкции машины вышеуказанной программы в архитектуре 32 битов.

область красной метки показывает, как адрес обменивается и сохраняется в eax.Затем их вызывает инструкция по eax. eax содержит желаемый адрес функции


указатель функции-это переменная, содержащая адрес функции. Поскольку это переменная указателя, хотя и с некоторыми ограниченными свойствами, вы можете использовать ее так же, как и любую другую переменную указателя в структурах данных.

единственное исключение, которое я могу придумать, это рассматривать указатель функции как указывающий на что-то другое, чем одно значение. Выполнение арифметики указателя путем увеличения или уменьшения указателя функции или добавления / вычитания смещения в указатель функции на самом деле не имеет никакой полезности, поскольку указатель функции указывает только на одну вещь, точку входа функции.

размер переменной указателя функции, количество байтов, занятых переменной, может варьироваться в зависимости от базовой архитектуры, например x32 или x64 или что-то еще.

объявление для переменной указателя функции должно указывать ту же информацию, что и объявление функции, чтобы компилятор C мог делать виды проверяет, что он обычно делает. Если вы не укажете список параметров в объявлении/определении указателя функции, компилятор C не сможет проверить использование параметров. Есть случаи, когда это отсутствие проверки может быть полезно, однако просто помните, что сеть безопасности была удалена.

примеры:

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.

первые два declararations чем-то похожи в том, что:

  • func - это функция, которая принимает int и char * и возвращает int
  • pFunc - это указатель на функцию, которой присваивается адрес функции, которая принимает int и char * и возвращает int

таким образом, из вышеизложенного мы могли бы иметь исходную строку, в которой адрес функции func() присваивается переменной указателя функции pFunc а в pFunc = func;.

обратите внимание на синтаксис, используемый с объявлением/определением указателя функции в которой скобки используются для преодоления правил приоритета естественного оператора.

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

Несколько Различных Примеров Использования

некоторые примеры использования указателя на функцию:

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

в определении указателя функции можно использовать списки параметров переменной длины.

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

или вы не можете указать список параметров вообще. Это может быть полезно, но это исключает возможность компилятора 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);

C стиль бросает

вы можете использовать слепки стиля C с указателями функций. Однако имейте в виду, что компилятор C может быть небрежным о проверках или предоставлять предупреждения, а не ошибки.

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.

сравнить указатель функции с равенством

вы можете проверить, что указатель функции равен определенному адресу функции с помощью if заявление, хотя я не уверен, что это будет полезно. Другие операторы сравнения, по-видимому, имеют еще меньшую полезность.

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

массив указателей функций

и если вы хотите иметь массив указателей функций, каждый из элементов которого список аргументов имеет различия, то вы можете определить указатель функции со списком аргументов неуказанным (не void что означает отсутствие аргументов, но просто не указано) что-то вроде следующего если вы можете видеть предупреждения компилятора C. Это также работает для параметра указателя функции на функцию:

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 namespace Использование Global struct с указателями на функции

вы можете использовать static ключевое слово, чтобы указать функцию, имя которой-область файла, а затем назначить это глобальной переменной как способ предоставления чего-то похожего на namespace функциональность C++.

в заголовочном файле определите структуру, которая будет нашей пространство имен вместе с глобальной переменной, которая его использует.

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;

затем в исходном файле 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};

затем это будет использоваться путем указания полного имени переменной global struct и имени члена для доступа к функции. The const модификатор используется на глобальном, так что он не может быть изменен случайно.

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

области применения указателей функций

компонент библиотеки DLL может что-то сделать похожие на The C style namespace подход, в котором конкретный интерфейс библиотеки запрашивается из Заводского метода в интерфейсе библиотеки, который поддерживает создание struct содержащие указатели на функции.. Этот интерфейс библиотеки загружает запрошенную версию DLL, создает структуру с необходимыми указателями функций, а затем возвращает структуру запрашивающему абоненту для использования.

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

и это можно использовать как в:

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

такой же подход может используется для определения абстрактного аппаратного уровня для кода, использующего определенную модель базового оборудования. Указатели функций заполняются заводом аппаратно-специфическими функциями для обеспечения аппаратно-специфической функциональности, реализующей функции, указанные в абстрактной аппаратной модели. Это может использоваться для предоставления абстрактного аппаратного уровня, используемого программным обеспечением, которое вызывает заводскую функцию, чтобы получить определенный интерфейс аппаратной функции, а затем использует указатели функций предоставлено для выполнения действий для базового оборудования без необходимости знать детали реализации о конкретной цели.

указатели функций для создания делегатов, обработчиков и обратных вызовов

вы можете использовать указатели на функции как способ делегировать некоторые задачи или функции. Классический пример в C-указатель функции делегата сравнения, используемый со стандартными функциями библиотеки C qsort() и bsearch() обеспечить порядок сортировки сортировка списка элементов или выполнение двоичного поиска по отсортированному списку элементов. Делегат функции сравнения задает алгоритм сортировки, используемый при сортировке или двоичном поиске.

другое использование аналогично применению алгоритма к контейнеру библиотеки стандартных шаблонов C++.

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

другой пример-исходный код GUI, в котором обработчик для определенного события регистрируется путем предоставления указателя функции, который фактически вызывается, когда событие случается. Платформа Microsoft MFC с картами сообщений использует нечто подобное для обработки сообщений Windows, доставляемых в окно или поток.

асинхронные функции, требующие обратного вызова, аналогичны обработчику событий. Пользователь асинхронной функции вызывает асинхронную функцию для запуска некоторого действия и предоставляет указатель функции, который асинхронная функция вызовет после завершения действия. В этом случае событие является асинхронным функция выполняет свою задачу.


поскольку указатели функций часто являются типизированными обратными вызовами, вы можете взглянуть на введите безопасные обратные вызовы. То же самое относится и к точкам входа и т. д. функций, которые не являются ответами.

C довольно непостоянен и в то же время прощает :)


указатели на функции полезны во многих ситуациях, например:

  • члены COM-объектов являются указателями на функцию ag: This->lpVtbl->AddRef(This); AddRef-указатель на функцию.
  • функция обратного вызова, например пользовательская функция для сравнения две переменные, передаваемые в качестве обратного вызова специальной функции сортировки.
  • очень полезно для реализации плагинов и приложений SDK.