Указатели функций в C - характер и использование

Я только что прочитал интересный вопрос здесь, что заставляет меня задаться вопросом о еще двух вещей:

  1. Зачем кому-то сравнивать указатели на функции, учитывая, что по понятию функции уникальность обеспечивается их разными именами?
  2. Компилятор видит в указателях функций как специальные указатели? Я имею в виду, что они видят их как, допустим, указатели на void * или содержат более богатую информацию (например, тип возврата, количество аргументов и типы аргументов?)

Ответ 1

  1. Зачем кому-то сравнивать указатели? Рассмотрим следующий сценарий -

    У вас есть массив указателей на функции, скажем, что это цепочка обратного вызова, и вам нужно позвонить каждому из них. Список завершается указателем функции NULL (или часового). Вам нужно сравнить, если вы достигли конца списка, сравнивая его с этим указателем-дозорником. Кроме того, этот случай оправдывает предыдущие OPs, что разные функции должны иметь разные указатели, даже если они похожи.

  2. Разве компилятор видит их по-другому? Да. Информация о типе включает всю информацию о аргументах и возвращаемом типе.

    Например, следующий код будет/должен быть отклонен компилятором -

    void foo(int a);
    void (*bar)(long) = foo; // Without an explicit cast
    

Ответ 2

Зачем кому-то сравнивать указатели на функции? Вот один пример:

#include <stdbool.h>

/*
 * Register a function to be executed on event. A function may only be registered once.
 * Input:
 *   arg - function pointer
 * Returns:
 *   true on successful registration, false if the function is already registered.
 */
bool register_function_for_event(void (*arg)(void));

/*
 * Un-register a function previously registered for execution on event.
 * Input:
 *   arg - function pointer
 * Returns:
 *   true on successful un-registration, false if the function was not registered.
 */
bool unregister_function_for_event(void (*arg)(void));

Тело register_function_for_event видит только arg. Он не видит имени функции. Он должен сравнивать указатели функций, чтобы сообщить, что кто-то регистрирует одну и ту же функцию дважды.

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

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

Ответ 3

  1. Зачем кому-то сравнивать указатели на функции, учитывая, что по понятию функции уникальность обеспечивается их разными именами?

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

Если у вас есть переменная, такая как

void (*fptr)(int);

он может указывать на любую функцию, которая принимает int как входную и возвращает void.

Скажем, у вас есть:

void function1(int)
{
}

void function2(int)
{
}

Ты можешь использовать:

fptr = function1;
foo(fptr);

или же:

fptr = function2;
foo(fptr);

Возможно, вы захотите сделать разные вещи в foo зависимости от того, указывает ли fptr на одну или другую функцию. Следовательно, необходимо:

if ( fptr == function1 )
{
    // Do stuff 1
}
else
{
    // Do stuff 2
}
  1. Компилятор видит в указателях функций как специальные указатели? Я имею в виду, что они видят их как, допустим, указатели на void * или содержат более богатую информацию (например, тип возврата, количество аргументов и типы аргументов?)

Да, указатели функций - это специальные указатели, отличные от указателей, указывающих на объекты.

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

Ответ 4

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

  • Как и другие указатели, указатели на функцию могут указывать на разные объекты в разное время, поэтому их сравнение может иметь смысл.
  • Указатели на функционирование являются специальными и не должны храниться в других типах указателей (даже не void * и даже на языке C).
  • Богатая часть (подпись функции) хранится в типе функции - причина приведенного выше предложения.

Но C имеет (декларирующий) режим объявления функции. В дополнение к режиму полного прототипа, который объявляет тип возврата и тип для всех параметров, C может использовать так называемый режим списка параметров, который является старым режимом K & R. В этом режиме декларация объявляет только возвращаемый тип:

int (*fptr)();

В C он объявляет указатель на функцию возврата int и принятия произвольных параметров. Просто это будет неопределенное поведение (UB), чтобы использовать его с неправильным списком параметров.

Итак, это законный код C:

#include <stdio.h>
#include <string.h>

int add2(int a, int b) {
    return a + b;
}
int add3(int a, int b, int c) {
    return a + b + c;
}

int(*fptr)();
int main() {
    fptr = add2;
    printf("%d\n", fptr(1, 2));
    fptr = add3;
    printf("%d\n", fptr(1, 2, 3));
    /* fprintf("%d\n", fptr(1, 2)); Would be UB */
    return 0;
}

Не притворяйтесь, что я посоветовал вам это сделать! В настоящее время он считается устаревшим признаком и его следует избегать. Я просто предупреждаю вас об этом. ИМХО может иметь только некоторые исключительные приемлемые варианты использования.

Ответ 5

Представьте себе, как реализовать функциональность, аналогичную функции WNDCLASS?

У него есть lpszClassName чтобы отличать классы окон друг от друга, но пусть вам не нужна (или не lpszClassName) строка, доступная для различения разных классов друг от друга.

Что у вас есть это окно класса процедура lpfnWndProc (типа WindowProc).

Итак, что бы вы сделали, если кто-то дважды lpfnWndProc RegisterClass с тем же lpfnWndProc?
Вам нужно как-то обнаружить повторные регистрации одного класса и вернуть ошибку.

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

Ответ 6

1) Есть много ситуаций. Возьмем, к примеру, типичную реализацию конечного автомата:

typedef void state_func_t (void);

const state_func_t* STATE_MACHINE[] =
{
  state_init,
  state_something,
  state_something_else
};

...

for(;;)
{
  STATE_MACHINE[state]();
}

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

if(STATE_MACHINE[state] == state_something)
{
  print_debug_stuff();
}

2) Да компилятор C видит их как разные типы. На самом деле указатели на функции имеют более строгую безопасность типов, чем другие типы C, потому что они не могут получить неявно преобразованные в/из void*, например, указатели на типы объектов. (C11 6.3.2.3/1). Также они не могут быть явно переданы в/из void* - при этом будут вызываться нестандартные расширения.

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

Ответ 7

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

C рассматривает указатели на функции с тем же списком аргументов и возвращаемым значением того же типа.