Рассмотрим signal()
функцию из стандарта C:
extern void (*signal(int, void(*)(int)))(int);
Совершенно очевидно - это функция, которая принимает два аргумента, целое число и указатель на функцию, которая принимает целое число в качестве аргумента и ничего не возвращает, и она ( signal()
) возвращает указатель на функцию, которая принимает целое число в качестве аргумента и возвращает ничего.
Если вы напишите:
typedef void (*SignalHandler)(int signum);
тогда вы можете вместо этого объявить signal()
как:
extern SignalHandler signal(int signum, SignalHandler handler);
Это означает то же самое, но обычно считается более легким для чтения. Понятно, что функция принимает a 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
a и a 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)();
представляет пустое значение. И поэтому:
extern void (*alt_signal(void))(int signum);
эквивалентно. Теперь signal()
он более сложный, потому что он не только возвращает a SignalHandler
, он также принимает в SignalHandler
качестве аргументов int и a :
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
Если это все еще смущает вас, я не уверен, как помочь - это все еще на некоторых уровнях загадочно для меня, но я привык к тому, как это работает, и поэтому могу сказать вам, что если вы будете придерживаться этого еще 25 лет или так, это станет вашей второй натурой (и, может быть, даже немного быстрее, если вы умны).