了解 C 中函数指针的类型定义 [英] Understanding typedefs for function pointers in C

查看:28
本文介绍了了解 C 中函数指针的类型定义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我阅读其他人的代码时,我总是有点困惑,这些代码具有用于指向带参数的函数的指针的类型定义.我记得我在尝试理解前段时间用 C 语言编写的数值算法时花了一段时间来理解这样的定义.那么,您能否分享关于如何为函数指针(Do's 和 Do's)编写好的 typedef 的技巧和想法,以及它们为什么有用以及如何理解其他人的工作?谢谢!

I have always been a bit stumped when I read other peoples' code which had typedefs for pointers to functions with arguments. I recall that it took me a while to get around to such a definition while trying to understand a numerical algorithm written in C a while ago. So, could you share your tips and thoughts on how to write good typedefs for pointers to functions (Do's and Do not's), as to why are they useful and how to understand others' work? Thanks!

推荐答案

考虑 C 标准中的 signal() 函数:

Consider the signal() function from the C standard:

extern void (*signal(int, void(*)(int)))(int);

完全模糊明显 - 它是一个函数,它接受两个参数,一个整数和一个指向一个函数的指针,该函数将一个整数作为参数并且不返回任何内容,并且它 (signal()) 返回一个指向以整数为参数且不返回任何内容的函数的指针.

Perfectly obscurely obvious - it's a function that takes two arguments, an integer and a pointer to a function that takes an integer as an argument and returns nothing, and it (signal()) returns a pointer to a function that takes an integer as an argument and returns nothing.

如果你写:

typedef void (*SignalHandler)(int signum);

那么你可以改为将 signal() 声明为:

then you can instead declare signal() as:

extern  SignalHandler signal(int signum, SignalHandler handler);

这意味着同样的事情,但通常被认为更容易阅读.更清楚的是,该函数接受一个 int 和一个 SignalHandler 并返回一个 SignalHandler.

This means the same thing, but is usually regarded as somewhat easier to read. It is clearer that the function takes an int and a SignalHandler and returns a SignalHandler.

不过需要一点时间来适应.但是,您不能做的一件事是使用函数定义中的 SignalHandler typedef 编写信号处理函数.

It takes a bit of getting used to, though. The one thing you can't do, though is write a signal handler function using the SignalHandler typedef in the function definition.

我仍然是那种喜欢调用函数指针的老派:

I'm still of the old-school that prefers to invoke a function pointer as:

(*functionpointer)(arg1, arg2, ...);

现代语法仅使用:

functionpointer(arg1, arg2, ...);

我明白为什么会这样了——我只是想知道我需要寻找变量的初始化位置,而不是一个名为 functionpointer 的函数.

I can see why that works - I just prefer to know that I need to look for where the variable is initialized rather than for a function called functionpointer.

山姆评论:

我以前看过这个解释.然后,就像现在的情况一样,我认为我没有得到两个陈述之间的联系:

I have seen this explanation before. And then, as is the case now, I think what I didn't get was the connection between the two statements:

    extern void (*signal(int, void()(int)))(int);  /*and*/

    typedef void (*SignalHandler)(int signum);
    extern SignalHandler signal(int signum, SignalHandler handler);

或者,我想问的是,人们可以用来提出您拥有的第二个版本的基本概念是什么?连接SignalHandler"的基础是什么?和第一个 typedef?我觉得这里需要说明的是typedef在这里实际上是做什么的.

Or, what I want to ask is, what is the underlying concept that one can use to come up with the second version you have? What is the fundamental that connects "SignalHandler" and the first typedef? I think what needs to be explained here is what is typedef is actually doing here.

我们再试一次.第一个是直接从 C 标准中提取的 - 我重新输入了它,并检查我的括号是否正确(直到我更正它 - 这是一个很难记住的饼干).

Let's try again. The first of these is lifted straight from the C standard - I retyped it, and checked that I had the parentheses right (not until I corrected it - it is a tough cookie to remember).

首先,请记住 typedef 为类型引入了别名.所以,别名是SignalHandler,它的类型是:

First of all, remember that typedef introduces an alias for a type. So, the alias is SignalHandler, and its type is:

一个指向一个函数的指针,该函数接受一个整数作为参数并且什么都不返回.

a pointer to a function that takes an integer as an argument and returns nothing.

'returns nothing' 部分拼写为 void;作为整数的参数是(我相信)不言自明的.以下符号只是(或不是)C 是如何拼写指向函数的指针的,该函数采用指定的参数并返回给定的类型:

The 'returns nothing' part is spelled void; the argument that is an integer is (I trust) self-explanatory. The following notation is simply (or not) how C spells pointer to function taking arguments as specified and returning the given type:

type (*function)(argtypes);

创建信号处理程序类型后,我可以使用它来声明变量等.例如:

After creating the signal handler type, I can use it to declare variables and so on. For example:

static void alarm_catcher(int signum)
{
    fprintf(stderr, "%s() called (%d)
", __func__, signum);
}

static void signal_catcher(int signum)
{
    fprintf(stderr, "%s() called (%d) - exiting
", __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 个标准标头之外,我们还做了什么?

So, what have we done here - apart from omit 4 standard headers that would be needed to make the code compile cleanly?

前两个函数是接受单个整数并且不返回任何内容的函数.由于 exit(1);,其中一个实际上根本不返回,但另一个在打印消息后确实返回.请注意,C 标准不允许您在信号处理程序中做太多事情;POSIX 在允许的范围内更慷慨一些,但正式不批准调用 fprintf().我还打印出收到的信号编号.在 alarm_handler() 函数中,值将始终为 SIGALRM 因为这是它作为处理程序的唯一信号,但是 signal_handler() 可能会得到 SIGINTSIGQUIT 作为信号编号,因为两者都使用相同的函数.

The first two functions are functions that take a single integer and return nothing. One of them actually doesn't return at all thanks to the exit(1); but the other does return after printing a message. Be aware that the C standard does not permit you to do very much inside a signal handler; POSIX is a bit more generous in what is allowed, but officially does not sanction calling fprintf(). I also print out the signal number that was received. In the alarm_handler() function, the value will always be SIGALRM as that is the only signal that it is a handler for, but signal_handler() might get SIGINT or SIGQUIT as the signal number because the same function is used for both.

然后我创建一个结构数组,其中每个元素标识一个信号编号和要为该信号安装的处理程序.我选择担心 3 个信号;我也经常担心 SIGHUPSIGPIPESIGTERM 以及它们是否被定义(#ifdef 条件编译),但这只会使事情复杂化.我也可能使用 POSIX sigaction() 而不是 signal(),但这是另一个问题;让我们坚持我们的开始.

Then I create an array of structures, where each element identifies a signal number and the handler to be installed for that signal. I've chosen to worry about 3 signals; I'd often worry about SIGHUP, SIGPIPE and SIGTERM too and about whether they are defined (#ifdef conditional compilation), but that just complicates things. I'd also probably use POSIX sigaction() instead of signal(), but that is another issue; let's stick with what we started with.

main() 函数遍历要安装的处理程序列表.对于每个处理程序,它首先调用 signal() 以查明进程当前是否正在忽略该信号,并在此过程中安装 SIG_IGN 作为处理程序,以确保信号被忽略.如果之前没有忽略该信号,则它会再次调用 signal(),这次是安装首选信号处理程序.(另一个值大概是 SIG_DFL,信号的默认信号处理程序.)因为对'signal()'的第一次调用将处理程序设置为SIG_IGNsignal() 返回之前的错误处理程序,if 语句之后的 old 的值必须是 SIG_IGN - 因此是断言.(好吧,如果出现严重错误,则可能是 SIG_ERR - 但随后我会从断言触发中了解到这一点.)

The main() function iterates over the list of handlers to be installed. For each handler, it first calls signal() to find out whether the process is currently ignoring the signal, and while doing so, installs SIG_IGN as the handler, which ensures that the signal stays ignored. If the signal was not previously being ignored, it then calls signal() again, this time to install the preferred signal handler. (The other value is presumably SIG_DFL, the default signal handler for the signal.) Because the first call to 'signal()' set the handler to SIG_IGN and signal() returns the previous error handler, the value of old after the if statement must be SIG_IGN - hence the assertion. (Well, it could be SIG_ERR if something went dramatically wrong - but then I'd learn about that from the assert firing.)

然后程序执行它的操作并正常退出.

The program then does its stuff and exits normally.

请注意,函数的名称可以看作是指向适当类型函数的指针.当您不应用函数调用括号时 - 例如在初始化程序中 - 函数名称变为函数指针.这也是为什么通过 pointertofunction(arg1, arg2) 符号调用函数是合理的;当你看到 alarm_handler(1) 时,你可以认为 alarm_handler 是一个指向函数的指针,因此 alarm_handler(1) 是一个调用通过函数指针的函数.

Note that the name of a function can be regarded as a pointer to a function of the appropriate type. When you do not apply the function-call parentheses - as in the initializers, for example - the function name becomes a function pointer. This is also why it is reasonable to invoke functions via the pointertofunction(arg1, arg2) notation; when you see alarm_handler(1), you can consider that alarm_handler is a pointer to the function and therefore alarm_handler(1) is an invocation of a function via a function pointer.

所以,到目前为止,我已经证明了 SignalHandler 变量使用起来相对简单,只要您有一些正确类型的值可以分配给它 - 即这两个信号处理函数提供了什么.

So, thus far, I've shown that a SignalHandler variable is relatively straight-forward to use, as long as you have some of the right type of value to assign to it - which is what the two signal handler functions provide.

现在我们回到问题 - signal() 的两个声明如何相互关联.

Now we get back to the question - how do the two declarations for signal() relate to each other.

让我们回顾一下第二个声明:

Let's review the second declaration:

 extern SignalHandler signal(int signum, SignalHandler handler);

如果我们像这样改变函数名和类型:

If we changed the function name and the type like this:

 extern double function(int num1, double num2);

你可以毫无问题地将它解释为一个函数,它接受一个 int 和一个 double 作为参数并返回一个 double 值(会你?也许你最好不要如果那是有问题的就坦白——但也许你应该谨慎地提出像这个问题一样困难的问题,如果这是一个问题).

you would have no problem interpreting this as a function that takes an int and a double as arguments and returns a double value (would you? maybe you'd better not 'fess up if that is problematic - but maybe you should be cautious about asking questions as hard as this one if it is a problem).

现在,signal() 函数接受一个 SignalHandler 作为它的第二个参数,而不是一个 double,它返回一个作为它的结果.

Now, instead of being a double, the signal() function takes a SignalHandler as its second argument, and it returns one as its result.

也可以将其视为以下机制:

The mechanics by which that can also be treated as:

extern void (*signal(int signum, void(*handler)(int signum)))(int signum);

很难解释 - 所以我可能会把它搞砸.这次我给出了参数名称 - 尽管名称并不重要.

are tricky to explain - so I'll probably screw it up. This time I've given the parameters names - though the names aren't critical.

一般来说,在C中,声明机制是这样的,如果你写:

In general, in C, the declaration mechanism is such that if you write:

type var;

然后当您编写 var 时,它表示给定 type 的值.例如:

then when you write var it represents a value of the given type. For example:

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在语法中被视为存储类,而staticextern都是存储类.>

In the standard, typedef is treated as a storage class in the grammar, rather like static and extern are storage classes.

typedef void (*SignalHandler)(int signum);

意味着当你看到一个SignalHandler类型的变量(比如alarm_handler)被调用为:

means that when you see a variable of type SignalHandler (say alarm_handler) invoked as:

(*alarm_handler)(-1);

结果具有 type void - 没有结果.而 (*alarm_handler)(-1); 是对带有参数 -1alarm_handler() 的调用.

the result has type void - there is no result. And (*alarm_handler)(-1); is an invocation of alarm_handler() with argument -1.

所以,如果我们声明:

extern SignalHandler alt_signal(void);

意思是:

(*alt_signal)();

代表一个空值.因此:

represents a void value. And therefore:

extern void (*alt_signal(void))(int signum);

是等价的.现在,signal() 更加复杂,因为它不仅返回一个 SignalHandler,它还接受一个 int 和一个 SignalHandler 作为参数:

is equivalent. Now, signal() is more complex because it not only returns a SignalHandler, it also accepts both an int and a SignalHandler as arguments:

extern void (*signal(int signum, SignalHandler handler))(int signum);

extern void (*signal(int signum, void (*handler)(int signum)))(int signum);

如果这仍然让您感到困惑,我不知道如何提供帮助 - 它在某种程度上对我来说仍然很神秘,但我已经习惯了它的工作方式,因此可以告诉您,如果您坚持下去再过 25 年左右,它就会成为你的第二天性(如果你很聪明,甚至可能更快一点).

If that still confuses you, I'm not sure how to help - it is still at some levels mysterious to me, but I've grown used to how it works and can therefore tell you that if you stick with it for another 25 years or so, it will become second nature to you (and maybe even a bit quicker if you are clever).

这篇关于了解 C 中函数指针的类型定义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆