Рассмотрим 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);
у вас не возникло бы проблем с интерпретацией этого как функции, которая принимает аргументы inta и 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 лет или так, это станет вашей второй натурой (и, может быть, даже немного быстрее, если вы умны).