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

1068

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

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

  • 29
    Также: Для небольшого углубленного анализа указателей C, см. Blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge . Кроме того, программирование с нуля показывает, как они работают на уровне машины. Понимание «модели памяти» Си очень полезно для понимания работы указателей Си.
  • 6
    Отличная информация Хотя по названию я бы ожидал увидеть объяснение того, как «работают указатели функций», а не как они кодируются :)
Показать ещё 1 комментарий
Теги:
function-pointers

12 ответов

1315
Лучший ответ

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

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

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

Во-первых, пусть определит указатель на функцию, которая получает 2 int и возвращает 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;
}
  • 15
    Спасибо за отличную информацию. Не могли бы вы немного рассказать о том, где используются указатели на функции или они особенно полезны?
  • 295
    "functionPtr = & addInt;" также может быть написано (и часто так) как "functionPtr = addInt;" что также допустимо, так как стандарт говорит, что имя функции в этом контексте преобразуется в адрес функции.
Показать ещё 10 комментариев
281

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

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

Далее мы можем продолжить полиморфизм в C.

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

  • Добавьте функцию, которая будет служить в качестве переопределяющего метода length.
  • Перейдите к "конструктору" и установите указатель на метод переопределения length.

Добавление переопределяющего метода length в ImmutableString может быть выполнено добавлением lengthOverrideMethod:

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

Затем указатель функции для метода length в конструкторе подключается к 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 в классе ImmutableString как класс String, теперь метод length будет ссылаться на поведение, определенное в функции lengthOverrideMethod.

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

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

  • 14
    Этот ответ ужасен! Это не только подразумевает, что ОО как-то зависит от точечной нотации, но и поощряет добавление мусора в ваши объекты!
  • 23
    Это ОО, хорошо, но не где-нибудь рядом с О-стилем. То, что вы безуспешно реализовали, это OO на основе прототипов в стиле Javascript. Чтобы получить ОО в стиле C ++ / Pascal, вам необходимо: 1. Иметь структуру const для виртуальной таблицы каждого класса с виртуальными членами. 2. Иметь указатель на эту структуру в полиморфных объектах. 3. Вызывайте виртуальные методы через виртуальную таблицу и все другие методы напрямую - обычно придерживаясь ClassName_methodName присвоении имен некоторым функциям ClassName_methodName . Только тогда вы получите те же затраты времени выполнения и хранилища, что и в C ++ и Pascal.
Показать ещё 6 комментариев
180

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

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

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

    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);
    
  • Напишите счетчик 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
    
  • Вы даже можете записать рекурсивную функцию, которая насчитывает 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);
    
  • 7
    Примечание: это не работает, если включена функция предотвращения выполнения данных (например, в Windows XP SP2 +), потому что строки C обычно не помечаются как исполняемые.
  • 1
    это будет работать на Visual Studio? Я получаю: ошибка C2440: «приведение типа»: невозможно преобразовать из «const char [68]» в «void (__cdecl *) (int *, int *)». Есть идеи?
Показать ещё 20 комментариев
96

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

#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);
}
  • 7
    Вы также должны передать указатель на указанные пользователем данные, если вы хотите каким-либо образом извлечь любой вывод из итераций (например, замыкания).
  • 0
    Согласовано. Все мои итераторы выглядят так: int (*cb)(void *arg, ...) . Возвращаемое значение итератора также позволяет мне остановиться рано (если не ноль).
24

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

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

(*D2)(char);

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

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

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

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

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

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

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

Верх вниз

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

ID0(<parameters>)

Следующее в описании (после "return" ) было указателем на. Позвольте включить его:

*ID0(<parameters>)

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

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

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

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

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. Не стесняйтесь редактировать/исправлять в нем вещи.

20

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

Они очень удобны в использовании, когда вам нужны разные функции в разное время или на разных этапах разработки. Например, я разрабатываю приложение на главном компьютере с консолью, но окончательная версия программного обеспечения будет размещена на 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 <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

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 *, ...);

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

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

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

или

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

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

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

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

  • 2
    Вы потеряете много времени на исполнение. Вместо этого вы можете использовать макрос, который включает и отключает раздел кода, основанный на Debug / Release.
13

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

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

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

Одним из больших применений для указателей на функции в 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;
}
3

Начиная с нуля, функция имеет некоторый адрес памяти от того места, где они начинают выполняться. В языке сборки они называются (вызывают "адрес памяти функции" ). Теперь вернитесь к функции C Если у функции есть адрес памяти, тогда с ними можно управлять указателями в C.So По правилам 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");
}

Изображение 4791 После этого давайте посмотрим, как машина подхватывает Them.Glimpse машинной инструкции вышеуказанной программы в 32-битной архитектуре.

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

1

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

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

Размер переменной указателя функции, количество байтов, занимаемых переменной, может варьироваться в зависимости от базовой архитектуры, например 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.

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

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

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

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

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

Компонент библиотеки DLL мог бы сделать что-то похожее на подход namespace стиля C, в котором конкретный интерфейс библиотеки запрашивается из фабричного метода в интерфейсе библиотеки, который поддерживает создание 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 - это указатель функции делегата сравнения, используемый со стандартными библиотечными функциями 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, которые доставляются в окно или поток.

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

0

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

C довольно изменчив и прощает одновременно:)

-7

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню