当我阅读其他人的代码时,我总是有点困惑,这些代码为带有参数的函数指针提供了typedefs。我记得,当我试图理解不久前用C语言编写的数值算法时,我花了一段时间才得到这样的定义。那么,关于如何为函数指针编写良好的typedef (Do's和Do's),以及它们为什么有用以及如何理解其他人的工作,您能分享一下您的技巧和想法吗?谢谢!


当前回答

使用typedef定义更复杂的类型,如函数指针

我将以用C定义状态机为例

    typedef  int (*action_handler_t)(void *ctx, void *data);

现在我们已经定义了一个名为action_handler的类型,它接受两个指针并返回一个int

定义状态机

    typedef struct
    {
      state_t curr_state;   /* Enum for the Current state */
      event_t event;  /* Enum for the event */
      state_t next_state;   /* Enum for the next state */
      action_handler_t event_handler; /* Function-pointer to the action */

     }state_element;

指向动作的函数指针看起来像一个简单的类型,typedef主要用于此目的。

我的所有事件处理程序现在都应该遵循action_handler定义的类型

    int handle_event_a(void *fsm_ctx, void *in_msg );

    int handle_event_b(void *fsm_ctx, void *in_msg );

引用:

Linden编写的C语言编程专家

其他回答

A function pointer is like any other pointer, but it points to the address of a function instead of the address of data (on heap or stack). Like any pointer, it needs to be typed correctly. Functions are defined by their return value and the types of parameters they accept. So in order to fully describe a function, you must include its return value and the type of each parameter is accepts. When you typedef such a definition, you give it a 'friendly name' which makes it easier to create and reference pointers using that definition.

例如,假设你有一个函数:

float doMultiplication (float num1, float num2 ) {
    return num1 * num2; }

然后是下面的类型定义:

typedef float(*pt2Func)(float, float);

可以用来指向这个doMulitplication函数。它只是定义一个指向函数的指针,该函数返回一个浮点数,并接受两个参数,每个参数都是float类型。这个定义有一个友好的名字pt2Func。请注意,pt2Func可以指向返回一个浮点数并接受2个浮点数的任何函数。

所以你可以创建一个指针指向doMultiplication函数,如下所示:

pt2Func *myFnPtr = &doMultiplication;

你可以使用这个指针调用函数,如下所示:

float result = (*myFnPtr)(2.0, 5.1);

这是一个很好的阅读:http://www.newty.de/fpt/index.html

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

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

非常明显-它是一个接受两个参数的函数,一个整数和一个以整数为参数且不返回任何值的函数指针,it (signal())返回一个以整数为参数且不返回任何值的函数指针。

如果你这样写:

typedef void (*SignalHandler)(int signum);

然后你可以将signal()声明为:

extern  SignalHandler signal(int signum, SignalHandler handler);

这句话的意思是一样的,但通常被认为更容易阅读。更清楚的是,该函数接受一个int和一个SignalHandler,并返回一个SignalHandler。

不过,这需要一点时间来适应。但有一件事是不能做的,那就是使用函数定义中的SignalHandler类型定义编写信号处理程序函数。

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

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

现代语法使用just:

functionpointer(arg1, arg2, ...);

我可以看到为什么这样工作-我只是更喜欢知道我需要寻找变量初始化的位置,而不是一个称为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); 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标准中提取的——我重新输入了它,并检查了我的括号是否正确(直到我纠正了它——这是一个很难记住的饼干)。

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

指向函数的指针,该函数以整数作为参数,不返回任何值。

'returns nothing'部分拼写为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个标准头,将需要使代码编译干净?

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个信号;我也经常担心SIGHUP, SIGPIPE和SIGTERM,以及它们是否被定义(#ifdef条件编译),但这只会使事情复杂化。我也可能会使用POSIX sigaction()而不是signal(),但这是另一个问题;让我们坚持我们开始的。

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.)

然后程序执行它的工作并正常退出。

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变量是相对简单的,只要您有一些正确类型的值要赋给它——这就是两个信号处理程序函数所提供的。

现在我们回到这个问题——signal()的两个声明是如何相互关联的。

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

 extern SignalHandler signal(int signum, SignalHandler handler);

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

 extern double function(int num1, double num2);

将其解释为一个以int和double作为参数并返回一个double值的函数是没有问题的(会吗?如果这有问题,也许你最好不要坦白——但如果这是一个问题,也许你应该谨慎地问像这个问题一样难的问题)。

现在,signal()函数不再是double类型,而是接受SignalHandler作为第二个参数,并返回1作为结果。

这一机制也可以被视为:

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

很难解释,所以我可能会搞砸。这一次我给出了参数名称——尽管名称并不重要。

一般来说,在C语言中,声明机制是这样的:

type var;

然后,当你写var时,它代表一个给定类型的值。例如:

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);

结果类型为void——没有结果。(* alarm_handler) (1);使用参数-1调用alarm_handler()。

如果我们声明:

extern SignalHandler alt_signal(void);

这意味着:

(*alt_signal)();

表示一个空值。因此:

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

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

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

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

如果这仍然让你感到困惑,我不知道如何帮助你——在某种程度上它对我来说仍然是神秘的,但我已经习惯了它是如何工作的,因此我可以告诉你,如果你再坚持25年左右,它将成为你的第二天性(如果你聪明的话,甚至可能更快一点)。

这是我作为练习写的关于函数指针和函数指针数组的最简单的例子。

    typedef double (*pf)(double x);  /*this defines a type pf */

    double f1(double x) { return(x+x);}
    double f2(double x) { return(x*x);}

    pf pa[] = {f1, f2};


    main()
    {
        pf p;

        p = pa[0];
        printf("%f\n", p(3.0));
        p = pa[1];
        printf("%f\n", p(3.0));
    }

宏呢?我已经看到,使用类型定义,我不能将一些东西强制转换为函数指针。所以我为它做了这个小宏,它能够做我到目前为止需要的所有事情(不知道它不能用于什么-我认为它基本上是一个类型定义,但有一个可选参数,使它在类型转换中可用):

#define funcptr_t(sym_name) void (*sym_name)(void)

// Declare a function pointer-returning function, and declare a function pointer variable.
funcptr_t (randomFunction(funcptr_t (func_ptr_variable)));
// Cast a variable to a function pointer
(funcptr_t()) some_variable;

PS:我做这个通用用途,不确定它是否可以修改为特定的功能使用。

Cdecl是一个很好的工具,可以用来破译奇怪的语法,比如函数指针声明。您也可以使用它来生成它们。

至于使复杂的声明更容易解析以便将来维护(自己或他人)的技巧,我建议创建小块的typedef,并将这些小块用作更大更复杂表达式的构建块。例如:

typedef int (*FUNC_TYPE_1)(void);
typedef double (*FUNC_TYPE_2)(void);
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2);

而不是:

typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void);

Cdecl可以帮助你解决这些问题:

cdecl> explain int (*FUNC_TYPE_1)(void)
declare FUNC_TYPE_1 as pointer to function (void) returning int
cdecl> explain double (*FUNC_TYPE_2)(void)
declare FUNC_TYPE_2 as pointer to function (void) returning double
cdecl> declare FUNC_TYPE_3 as pointer to function (pointer to function (void) returning double) returning pointer to function (void) returning int
int (*(*FUNC_TYPE_3)(double (*)(void )))(void )

这(事实上)正是我如何产生上面那个疯狂的混乱。