Указатели функций в C - характер и использование
Я только что прочитал интересный вопрос здесь, что заставляет меня задаться вопросом о еще двух вещей:
- Зачем кому-то сравнивать указатели на функции, учитывая, что по понятию функции уникальность обеспечивается их разными именами?
- Компилятор видит в указателях функций как специальные указатели? Я имею в виду, что они видят их как, допустим, указатели на
void *
или содержат более богатую информацию (например, тип возврата, количество аргументов и типы аргументов?)
Ответы
Ответ 1
-
Зачем кому-то сравнивать указатели? Рассмотрим следующий сценарий -
У вас есть массив указателей на функции, скажем, что это цепочка обратного вызова, и вам нужно позвонить каждому из них. Список завершается указателем функции NULL
(или часового). Вам нужно сравнить, если вы достигли конца списка, сравнивая его с этим указателем-дозорником. Кроме того, этот случай оправдывает предыдущие OPs, что разные функции должны иметь разные указатели, даже если они похожи.
-
Разве компилятор видит их по-другому? Да. Информация о типе включает всю информацию о аргументах и возвращаемом типе.
Например, следующий код будет/должен быть отклонен компилятором -
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
- Зачем кому-то сравнивать указатели на функции, учитывая, что по понятию функции уникальность обеспечивается их разными именами?
Указатель функции может указывать на разные функции в разное время в программе.
Если у вас есть переменная, такая как
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
}
- Компилятор видит в указателях функций как специальные указатели? Я имею в виду, что они видят их как, допустим, указатели на 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 рассматривает указатели на функции с тем же списком аргументов и возвращаемым значением того же типа.