理解C中函数指针的typedef

Understanding typedefs for function pointers in C

当我阅读其他人的代码时,我总是有点困惑,因为这些代码中有指向带有参数的函数的指针的typedef。我记得我花了一段时间来理解这样一个定义,同时试图理解不久前用C语言编写的一个数值算法。那么,你能分享一下你的技巧和想法吗?如何为函数指针(做的和不做的)编写好的typedef,为什么它们有用,以及如何理解别人的工作?谢谢!


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

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

非常明显-它是一个函数,接受两个参数,一个整数和一个指向一个函数的指针,该函数接受一个整数作为参数,不返回任何内容,它(signal()返回一个指向一个函数的指针,该函数接受一个整数作为参数,不返回任何内容。好的。

如果你写:好的。

1
typedef void (*SignalHandler)(int signum);

然后您可以将signal()声明为:好的。

1
extern  SignalHandler signal(int signum, SignalHandler handler);

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

不过,这需要一点习惯。但是有一件事你不能做,那就是在函数定义中使用SignalHandlertypedef编写一个信号处理函数。好的。

我还是老派,喜欢调用函数指针:好的。

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

现代语法仅使用:好的。

1
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:

Ok.

1
2
3
4
    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.

Ok.

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

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

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

Ok.

"无回报"部分拼写为void;整数的论点是(我相信)不言自明的。下面的符号只是(或不是)C如何拼写指向函数的指针,将参数视为指定参数并返回给定类型:好的。

1
type (*function)(argtypes);

在创建了信号处理程序类型之后,我可以使用它来声明变量等等。例如:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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个标准头之外,这些头将需要使代码干净地编译?好的。

前两个函数是接受单个整数而不返回任何值的函数。由于exit(1);,其中一个实际上根本不返回,但另一个在打印消息后返回。请注意,C标准不允许您在信号处理程序中做太多的事情;POSIX在允许的方面有点宽宏大量,但官方并不批准调用fprintf()。我还打印出接收到的信号号。在alarm_handler()函数中,值始终是SIGALRM,因为这是它作为处理程序的唯一信号,但是signal_handler()可能会得到SIGINTSIGQUIT作为信号号,因为两者都使用相同的函数。好的。

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

main()函数迭代要安装的处理程序列表。对于每个处理程序,它首先调用signal()来查明进程当前是否忽略了信号,并在执行此操作时安装SIG_IGN作为处理程序,以确保信号保持被忽略。如果之前没有忽略该信号,则再次调用signal(),这次安装首选信号处理程序。(另一个值假定为SIG_DFL,信号的默认信号处理程序。)因为第一个调用'signal()'将处理程序设置为SIG_IGNsignal()返回上一个错误处理程序,所以if语句后的old值必须是SIG_IGN—因此断言。(好吧,如果出了大问题,可能是SIG_ERR——但我会从断言射击中了解到这一点。)好的。

然后程序执行其任务并正常退出。好的。

请注意,函数名可以被视为指向适当类型函数的指针。如果不应用函数调用括号(例如初始值设定项中的括号),则函数名将变为函数指针。这也是为什么通过pointertofunction(arg1, arg2)符号调用函数是合理的;当您看到alarm_handler(1)时,可以认为alarm_handler是指向函数的指针,因此alarm_handler(1)是通过函数指针调用函数的原因。好的。

因此,到目前为止,我已经证明了SignalHandler变量是相对直接的,只要您有一些正确类型的值分配给它,这就是两个信号处理函数所提供的。好的。

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

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

1
 extern SignalHandler signal(int signum, SignalHandler handler);

如果我们像这样更改了函数名和类型:好的。

1
 extern double function(int num1, double num2);

如果把它解释为一个以intdouble为参数并返回double值的函数,您会毫不困难的(是吗?如果有问题的话,也许你最好不要"坦白"——但是如果有问题的话,也许你应该谨慎地提出和这个问题一样难的问题。好的。

现在,signal()函数不再是double函数,而是将SignalHandler作为第二个参数,并返回一个作为结果。好的。

也可被视为:好的。

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

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

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

1
type var;

然后当你写var时,它代表给定type的值。例如:好的。

1
2
3
4
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是存储类。好的。

1
typedef void (*SignalHandler)(int signum);

意味着当您看到一个类型为SignalHandler的变量(比如报警处理程序)被调用为:好的。

1
(*alarm_handler)(-1);

结果是type void——没有结果。而(*alarm_handler)(-1);alarm_handler()的一个调用,带有-1的论点。好的。

因此,如果我们声明:好的。

1
extern SignalHandler alt_signal(void);

这意味着:好的。

1
(*alt_signal)();

表示空值。因此:好的。

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

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

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

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

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


函数指针与任何其他指针类似,但它指向函数的地址,而不是数据的地址(在堆或堆栈上)。像任何指针一样,它需要正确地键入。函数由其返回值和接受的参数类型定义。因此,为了充分描述一个函数,必须包含它的返回值,并且接受每个参数的类型。当您对这样的定义进行typedef时,会给它一个"友好名称",这使得使用该定义创建和引用指针变得更加容易。

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

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

然后是以下typedef:

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

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

因此,您可以创建一个指向domultiplication函数的指针,如下所示:

1
pt2Func *myFnPtr = &doMultiplication;

您可以使用以下指针调用函数:

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

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


一种很容易理解函数指针typedef的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int add(int a, int b)
{
    return (a+b);
}

typedef int (*add_integer)(int, int); //declaration of function pointer

int main()
{
    add_integer addition = add; //typedef assigns a new variable i.e."addition" to original function"add"
    int c = addition(11, 11);   //calling function via new variable
    printf("%d",c);
    return 0;
}

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

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

1
2
3
typedef int (*FUNC_TYPE_1)(void);
typedef double (*FUNC_TYPE_2)(void);
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2);

而不是:

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

cdecl可以帮助您解决以下问题:

1
2
3
4
5
6
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 )

事实上,这正是我在上面制造出的疯狂混乱。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int add(int a, int b)
{
  return (a+b);
}
int minus(int a, int b)
{
  return (a-b);
}

typedef int (*math_func)(int, int); //declaration of function pointer

int main()
{
  math_func addition = add;  //typedef assigns a new variable i.e."addition" to original function"add"
  math_func substract = minus; //typedef assigns a new variable i.e."substract" to original function"minus"

  int c = addition(11, 11);   //calling function via new variable
  printf("%d
"
,c);
  c = substract(11, 5);   //calling function via new variable
  printf("%d",c);
  return 0;
}

其输出为:

22

6

注意,在声明这两个函数时使用了相同的math-func定义符。

typedef的相同方法也可用于外部结构(在其他文件中使用struct)。


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

我将以在C中定义状态机为例

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

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

定义状态机

1
2
3
4
5
6
7
8
    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主要用于此目的。

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

1
2
3
    int handle_event_a(void *fsm_ctx, void *in_msg );

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

参考文献:

Linden的C编程专家


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    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
"
, p(3.0));
        p = pa[1];
        printf("%f
"
, p(3.0));
    }