考虑signal()
C 标准中的函数:
extern void (*signal(int, void(*)(int)))(int);
非常晦涩明显 - 它是一个接受两个参数的函数,一个整数和一个指向函数的指针,该函数接受整数作为参数并且不返回任何内容,并且它(signal()
) 返回一个指向函数的指针,该函数采用整数作为参数并且不返回任何内容。
如果你写:
typedef void (*SignalHandler)(int signum);
那么你可以声明signal()
as:
extern SignalHandler signal(int signum, SignalHandler handler);
这意味着同样的事情,但通常被认为更容易阅读。更清楚的是,该函数需要一个int
and a SignalHandler
并返回一个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 标准中提取的 - 我重新输入了它,并检查了括号是否正确(直到我更正它 - 这是一个很难记住的饼干)。
首先,请记住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);
}
Please note How to avoid using printf() in a signal handler? https://stackoverflow.com/questions/16891019
那么,除了省略使代码干净编译所需的 4 个标准标头之外,我们在这里做了什么?
前两个函数是采用单个整数且不返回任何内容的函数。其中一个实际上根本没有回来,这要归功于exit(1);
但另一个在打印消息后确实返回。请注意,C 标准不允许您在信号处理程序中执行太多操作;POSIX http://www.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04在允许的范围内更慷慨一些,但官方不批准打电话fprintf()
。我还打印出收到的信号号。在里面alarm_handler()
函数,值永远是SIGALRM
因为这是它作为处理程序的唯一信号,但是signal_handler()
也许能得到SIGINT
or SIGQUIT
作为信号编号,因为两者使用相同的函数。
然后,我创建一个结构数组,其中每个元素标识一个信号编号以及要为该信号安装的处理程序。我选择担心 3 个信号;我常常会担心SIGHUP
, SIGPIPE
and SIGTERM
以及它们是否被定义(#ifdef
条件编译),但这只会使事情变得复杂。我也可能会使用 POSIXsigaction()
代替signal()
,但那是另一个问题;让我们坚持我们开始的内容。
The main()
函数迭代要安装的处理程序列表。对于每个处理程序,它首先调用signal()
找出进程当前是否忽略该信号,并在执行此操作时安装SIG_IGN
作为处理程序,这确保信号保持被忽略。如果该信号之前没有被忽略,那么它会调用signal()
再次,这次安装首选信号处理程序。 (另一个值大概是 SIG_DFL
,信号的默认信号处理程序。)因为第一次调用“signal()”将处理程序设置为SIG_IGN
and 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
and a double
作为参数并返回double
价值(你会吗?如果这是有问题的,也许你最好不要“坦白”——但如果这是一个问题,也许你应该谨慎地提出像这个这样的问题)。
现在,不再是一个double
, the signal()
函数需要一个SignalHandler
作为其第二个参数,并返回 1 作为结果。
其机制也可以被视为:
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
and 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()
更复杂,因为它不仅返回一个SignalHandler
,它也接受 int 和 aSignalHandler
作为参数:
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
如果这仍然让你感到困惑,我不知道如何提供帮助 - 它在某种程度上对我来说仍然很神秘,但我已经习惯了它的工作原理,因此可以告诉你,如果你再坚持 25 年或者这样,这将成为你的第二天性(如果你聪明的话,甚至可能会更快一些)。