Ответ 1
Рассмотрим функцию signal()
из стандарта C:
extern void (*signal(int, void(*)(int)))(int);
Совершенно неясно очевидно - это функция, которая принимает два аргумента, целое число и указатель на функцию, которая принимает целое число в качестве аргумента и ничего не возвращает, и она (signal()
) возвращает указатель на функцию, которая принимает целое число как аргумент и ничего не возвращает.
Если вы пишете:
typedef void (*SignalHandler)(int signum);
то вы можете вместо этого объявить signal()
как:
extern SignalHandler signal(int signum, SignalHandler handler);
Это означает то же самое, но обычно считается несколько более легким для чтения. Понятно, что функция принимает int
и a SignalHandler
и возвращает a SignalHandler
.
Придется немного привыкнуть. Единственное, что вы не можете сделать, - это написать функцию обработчика сигнала, используя SignalHandler
typedef
в определении функции.
Я все еще из старой школы, которая предпочитает ссылаться на указатель функции как:
(*functionpointer)(arg1, arg2, ...);
Современный синтаксис использует только:
functionpointer(arg1, arg2, ...);
Я вижу, почему это работает - я просто предпочитаю знать, что мне нужно искать, где инициализируется переменная, а не для функции с именем functionpointer
.
Сэм прокомментировал:
Я уже видел это объяснение. И тогда, как и сейчас, я думаю, что я не получал связь между двумя утверждениями:
extern void (*signal(int, void()(int)))(int); /*and*/ typedef void (*SignalHandler)(int signum); extern SignalHandler signal(int signum, SignalHandler handler);
Или, что я хочу спросить, какова основная концепция, которую можно использовать, чтобы придумать вторую версию, которую у вас есть? Что является фундаментальным, что связывает "SignalHandler" и первый typedef? Я думаю, что здесь должно быть объяснено то, что на самом деле делает typedef.
Повторите попытку. Первый из них поднят прямо из стандарта C - я перепечатал его и проверил, что у меня есть круглые скобки справа (не до тех пор, пока я не исправил его - это жесткий файл cookie, чтобы помнить).
Прежде всего, помните, что typedef
вводит псевдоним для типа. Итак, псевдоним SignalHandler
, и его тип:
указатель на функцию, которая принимает целое число в качестве аргумента и ничего не возвращает.
Часть "возвращает ничего" написана void
; аргумент, являющийся целым числом, является (я уверен) самоочевидным. Следующие обозначения просто (или нет), как C заклинания указатель на функцию принятия аргументов, как указано, и возвращения данного типа:
type (*function)(argtypes);
После создания типа обработчика сигнала я могу использовать его для объявления переменных и т.д. Например:
static void alarm_catcher(int signum)
{
fprintf(stderr, "%s() called (%d)\n", __func__, signum);
}
static void signal_catcher(int signum)
{
fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum);
exit(1);
}
static struct Handlers
{
int signum;
SignalHandler handler;
} handler[] =
{
{ SIGALRM, alarm_catcher },
{ SIGINT, signal_catcher },
{ SIGQUIT, signal_catcher },
};
int main(void)
{
size_t num_handlers = sizeof(handler) / sizeof(handler[0]);
size_t i;
for (i = 0; i < num_handlers; i++)
{
SignalHandler old_handler = signal(handler[i].signum, SIG_IGN);
if (old_handler != SIG_IGN)
old_handler = signal(handler[i].signum, handler[i].handler);
assert(old_handler == SIG_IGN);
}
...continue with ordinary processing...
return(EXIT_SUCCESS);
}
Обратите внимание на Как избежать использования printf()
в обработчике сигналов?
Итак, что мы здесь сделали - кроме опускания 4 стандартных заголовков, которые необходимы для того, чтобы код был скомпилирован?
Первые две функции - это функции, которые берут одно целое и не возвращают ничего. Один из них фактически не возвращается вообще благодаря exit(1);
, а другой возвращает после печати сообщения. Имейте в виду, что стандарт C не позволяет вам делать очень многое внутри обработчика сигнала; POSIX является немного более щедрым в том, что разрешено, но официально не санкционирует вызов fprintf()
. Я также распечатываю полученный номер сигнала. В функции alarm_handler()
значение всегда будет SIGALRM
, так как это единственный сигнал, которым он является обработчик, но signal_handler()
может получить SIGINT
или SIGQUIT
в качестве номера сигнала, поскольку эта же функция используется для обоих.
Затем я создаю массив структур, где каждый элемент идентифицирует номер сигнала и обработчик, который должен быть установлен для этого сигнала. Я решил беспокоиться о 3 сигналах; Я часто беспокоюсь о SIGHUP
, SIGPIPE
и SIGTERM
тоже и о том, определены ли они (#ifdef
условная компиляция), но это просто усложняет ситуацию. Я бы также использовал POSIX sigaction()
вместо signal()
, но это еще одна проблема; пусть будет придерживаться того, с чего мы начали.
Функция main()
выполняет итерацию по списку установленных обработчиков. Для каждого обработчика он сначала вызывает signal()
, чтобы узнать, игнорирует ли этот процесс данный процесс, и при этом устанавливает SIG_IGN
в качестве обработчика, который гарантирует, что сигнал остается игнорированным. Если ранее сигнал не был проигнорирован, он снова вызывает signal()
, на этот раз для установки предпочтительного обработчика сигнала. (Другое значение, по-видимому, SIG_DFL
, обработчик сигнала по умолчанию для сигнала.) Поскольку первый вызов "signal()" устанавливает обработчик на SIG_IGN
и signal()
, возвращает предыдущий обработчик ошибок, значение old
после оператора if
должно быть SIG_IGN
- следовательно, утверждение. (Ну, это могло бы быть SIG_ERR
, если что-то пошло не так, но потом я узнаю об этом из-за увольнения.)
Затем программа выполняет свои действия и обычно выходит.
Обратите внимание, что имя функции можно рассматривать как указатель на функцию соответствующего типа. Если вы не применяете круглые скобки функций - например, в инициализаторах - имя функции становится указателем на функцию. Именно поэтому разумно вызывать функции через нотацию pointertofunction(arg1, arg2)
; когда вы видите alarm_handler(1)
, вы можете считать, что alarm_handler
является указателем на функцию, и поэтому alarm_handler(1)
является вызовом функции с помощью указателя функции.
Итак, до сих пор я показал, что переменная SignalHandler
относительно проста в использовании, если у вас есть подходящий тип значения для ее назначения - это то, что два обработчика сигнала функции обеспечивают.
Теперь мы вернемся к вопросу - как два объявления для signal()
относятся друг к другу.
Просмотрите второе объявление:
extern SignalHandler signal(int signum, SignalHandler handler);
Если мы изменили имя функции и тип вроде этого:
extern double function(int num1, double num2);
у вас не возникло бы проблемы с интерпретацией этого как функции, которая принимает аргументы int
и double
в качестве аргументов и возвращает значение double
(не так ли? возможно, вам лучше не "испортиться", если это проблематично - но, может быть, вы должны быть осторожны, задавая вопросы так же сложно, как это, если это проблема).
Теперь вместо double
функция signal()
принимает в качестве второго аргумента SignalHandler
и возвращает ее как результат.
Механика, с помощью которой это также можно трактовать как:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
сложно объяснить, поэтому я, вероятно, испортил это. На этот раз я дал имена параметров, хотя имена не являются критическими.
В общем случае в C механизм объявления таков, что если вы пишете:
type var;
тогда, когда вы пишете var
, оно представляет значение данного type
. Например:
int i; // i is an int
int *ip; // *ip is an int, so ip is a pointer to an integer
int abs(int val); // abs(-1) is an int, so abs is a (pointer to a)
// function returning an int and taking an int argument
В стандарте typedef
рассматривается как класс хранения в грамматике, скорее, как static
и extern
являются классами хранения.
typedef void (*SignalHandler)(int signum);
означает, что когда вы видите переменную типа SignalHandler
(скажем, alarm_handler), вызывается как:
(*alarm_handler)(-1);
результат имеет type void
- результата нет. И (*alarm_handler)(-1);
является вызовом alarm_handler()
с аргументом -1
.
Итак, если мы объявили:
extern SignalHandler alt_signal(void);
это означает, что:
(*alt_signal)();
представляет значение void. И поэтому:
extern void (*alt_signal(void))(int signum);
эквивалентен. Теперь signal()
является более сложным, поскольку он возвращает не только SignalHandler
, но и принимает как int, так и a SignalHandler
в качестве аргументов:
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
Если это все еще вас смущает, я не уверен, как помочь - это все еще на некоторых уровнях таинственно для меня, но я привык к тому, как это работает, и поэтому может сказать вам, что если вы придерживаетесь его для еще 25 лет или около того, это станет для вас второй натурой (а может быть, даже немного быстрее, если вы умны).