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

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

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

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

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

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

Во-первых, давайте определим указатель на функцию, которая получает 2 int s и возвращает 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 оператора – это мертвая отдача, но, похоже, это означает, что мы устанавливаем текст какого-либо classа String как "hello" .

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

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

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

Как видно, методы classа String самом деле являются указателями на функцию объявленной функции. При подготовке экземпляра String newString функция 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.

Предположим, мы хотим создать подclass 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); 

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

Использование указателя функции может обеспечить наследование метода из суперclassа.

Мы можем далее продолжить polymorphism в C.

Если, например, мы хотели поменять поведение метода length чтобы по какой-то причине возвращать 0 в classе ImmutableString , все, что нужно сделать, это:

  1. Добавьте функцию, которая будет использоваться как метод переопределения length .
  2. Перейдите к «конструктору» и установите указатель на метод переопределенной length .

Добавление метода lengthOverrideMethod в lengthOverrideMethod может быть выполнено путем добавления lengthOverrideMethod :

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

Затем указатель функции для метода length в конструкторе lengthOverrideMethod к 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; } 

Теперь вместо того, чтобы иметь идентичное поведение для метода length в classе ImmutableString в качестве classа String , теперь метод length будет ссылаться на поведение, определенное в функции lengthOverrideMethod .

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

Для получения дополнительной информации о том, как выполнять объектно-ориентированное программирование на C, обратитесь к следующим вопросам:

  • Объектно-ориентированная в C?
  • Можете ли вы написать объектно-ориентированный код в C?

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

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

     int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))(); 
  2. Запись функции свопинга

     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. Напишите счетчик для цикла для 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); 

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

 #include  #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; iname) 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() : функция D, принимающая < параметры > возвращающая

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

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

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

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

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

Вверх дном

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

 D1(char); 

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

 (*D2)(char); 

Тип возврата завершен! Теперь заменим D2 функцией declarator функции, возвращающей , которая является D3() которой мы сейчас являемся.

 (*D3())(char) 

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

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

Я заменил D2 на ID1 , так как мы закончили с этим параметром (он уже является указателем на функцию - нет необходимости в другом деклараторе). ID1 будет именем параметра. Теперь, я сказал выше, в конце добавляется тип, который все эти деклараторы изменяют - тот, который появляется в самом левом углу каждого объявления. Для функций это становится возвращаемым типом. Для указателей указывается тип и т. Д. Интересно, когда записывается тип, он будет отображаться в обратном порядке, при самом правильном 🙂 Во всяком случае, подставляя его, получается полное объявление. Оба раза в курсе.

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

Я назвал идентификатор функции ID0 в этом примере.

Низходящий

Это начинается с идентификатора в самом левом в описании типа, обертывающего этот декларатор, когда мы проходим через право. Начните с возврата функции < parameters >

 ID0() 

Следующее в описании (после «возвращения») было указателем на . Давайте включим его:

 *ID0() 

Затем следующей была функция functon, возвращающая < parameters > . Параметр является простым символом, поэтому мы снова его разместим, так как это действительно тривиально.

 (*ID0())(char) 

Обратите внимание на скобки, которые мы добавили, так как мы снова хотим, чтобы сначала связывался * , а затем (char) . В противном случае он будет читать функцию, возвращающую функцию < parameters > .... Noes, функции возврата функций даже не разрешены.

Теперь нам просто нужно поставить < parameters > . Я покажу короткую версию деривации, так как я думаю, что у вас уже есть идея, как это сделать.

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

Просто поместите int перед деклараторами, как мы делали с восходящим, и мы закончили

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

Хорошая вещь

Лучше ли снизу вверх или сверху вниз? Я привык к восходящему, но некоторые люди могут быть более комфортными с нисходящим. Думаю, это вопрос вкуса. Кстати, если вы примените все операторы в этом объявлении, вы получите в итоге int:

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

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

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

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

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

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

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

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

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

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

В board_init() обратите внимание на board_init() где zprintf назначается уникальная функция (подпись функции которой соответствует) в зависимости от версии, определенной в version.h

zprintf = &printf; zprintf вызывает printf для целей отладки

или

zprintf = &noprint; zprintf просто возвращает и не будет запускать ненужный код

Запуск кода будет выглядеть так:

mainProg.c

 #include "version.h" #include  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; } 

Вышеприведенный код будет использовать printf если в режиме отладки, или ничего не делать, если в режиме деблокирования. Это намного проще, чем пройти весь проект и комментировать или удалять код. Все, что мне нужно сделать, это изменить версию в version.h и код сделает все остальное!

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

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

 #include  #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 ∑ } // test - use function pointer as variable, void test_pointer_as_variable() { // create a pointer to function, two_num_operation sum_p = ∑ // 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; } к 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.So. По правилам C

1. Сначала вам нужно объявить указатель на функцию 2.Pass Address of the Desired function

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

Эта простая программа будет иллюстрировать каждую вещь.

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

введите описание изображения здесь After That позволяет увидеть, как машина подхватывает Them.Glimpse машинной инструкции вышеуказанной программы в 32-битной архитектуре.

В области красных отметок показано, как происходит обмен и хранение адреса в eax.Then – это команда вызова на eax. eax содержит желаемый адрес функции

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

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

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

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

Некоторые примеры:

 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. 

Первые две декларации несколько схожи:

  • 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. } 

Пространство namespace стиля C Использование глобальной 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}; 

This would then be used by specifying the complete name of global struct variable and member name to access the function. The const modifier is used on the global so that it can not be changed by accident.

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

Application Areas of Function Pointers

A DLL library component could do something similar to the C style namespace approach in which a particular library interface is requested from a factory method in a library interface which supports the creation of a struct containing function pointers.. This library interface loads the requested DLL version, creates a struct with the necessary function pointers, and then returns the struct to the requesting caller for use.

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

and this could be used as in:

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

The same approach can be used to define an abstract hardware layer for code that uses a particular model of the underlying hardware. Function pointers are filled in with hardware specific functions by a factory to provide the hardware specific functionality that implements functions specified in the abstract hardware model. This can be used to provide an abstract hardware layer used by software which calls a factory function in order to get the specific hardware function interface then uses the function pointers provided to perform actions for the underlying hardware without needing to know implementation details about the specific target.

Function Pointers to create Delegates, Handlers, and Callbacks

You can use function pointers as a way to delegate some task or functionality. The classic example in C is the comparison delegate function pointer used with the Standard C library functions qsort() and bsearch() to provide the collation order for sorting a list of items or performing a binary search over a sorted list of items. The comparison function delegate specifies the collation algorithm used in the sort or the binary search.

Another use is similar to applying an algorithm to a C++ Standard Template Library container.

 void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) { unsigned char *pList = pArray; unsigned char *pListEnd = pList + nItems * sizeItem; for ( ; pList < pListEnd; pList += sizeItem) { p (pList); } return pArray; } int pIncrement(int *pI) { (*pI)++; return 1; } void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) { unsigned char *pList = pArray; unsigned char *pListEnd = pList + nItems * sizeItem; for (; pList < pListEnd; pList += sizeItem) { p(pList, pResult); } return pArray; } int pSummation(int *pI, int *pSum) { (*pSum) += *pI; return 1; } // source code and then lets use our function. int intList[30] = { 0 }, iSum = 0; ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement); ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation); 

Another example is with GUI source code in which a handler for a particular event is registered by providing a function pointer which is actually called when the event happens. The Microsoft MFC framework with its message maps uses something similar to handle Windows messages that are delivered to a window or thread.

Asynchronous functions that require a callback are similar to an event handler. The user of the asynchronous function calls the asynchronous function to start some action and provides a function pointer which the asynchronous function will call once the action is complete. In this case the event is the asynchronous function completing its task.

Since function pointers are often typed callbacks, you might want to have a look at type safe callbacks . The same applies to entry points, etc of functions that are not callbacks.

C is quite fickle and forgiving at the same time 🙂

function pointers are useful in many situations, eg:

  • COM objects members are pointer to function ag: This->lpVtbl->AddRef(This); AddRef is a pointer to a function.
  • function callback, for example a user defined function to compares two variables to be passed as a callback to a special sort function.
  • very useful for plugin implementation and application SDK.
  • C ++ Call Pointer To Member Function
  • Как hash и сравнить функцию-указатель-член?
  • convert std :: bind to function pointer
  • Давайте будем гением компьютера.