了解 C 中函数指针的 typedef

Posted

技术标签:

【中文标题】了解 C 中函数指针的 typedef【英文标题】:Understanding typedefs for function pointers in C 【发布时间】:2010-12-08 03:52:29 【问题描述】:

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

【问题讨论】:

你能提供一些例子吗? 你不是指函数指针的typedef,而不是函数指针的宏吗?我见过前者,但没有见过后者。 另见How to declare an __stdcall function pointer。 【参考方案1】:

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

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

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

如果你写:

typedef void (*SignalHandler)(int signum);

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

extern  SignalHandler signal(int signum, SignalHandler handler);

这意味着同样的事情,但通常被认为更容易阅读。更清楚的是,该函数接受 intSignalHandler 并返回 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,其类型为:

一个指向函数的指针,该函数接受一个整数作为参数并且不返回任何内容。

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

请注意How to avoid using printf() in a signal handler?

那么,除了省略 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 语句之后的 987654364@ 必须是 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);

您可以毫无问题地将其解释为一个以intdouble 作为参数并返回double 值的函数(你愿意吗?如果有问题,你最好不要“坦白” - 但如果是一个问题,也许你应该谨慎地提出像这个这样难以回答的问题)。

现在,signal() 函数不再是 double,而是将 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 在语法中被视为存储类,就像staticextern 是存储类一样。

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 和 SignalHandler 作为参数:

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

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

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

【讨论】:

我以前看过这个解释。然后,就像现在的情况一样,我认为我没有得到的是两个语句之间的联系: extern void (signal(int, void()(int)))(int) ;/*和*/ typedef void (*SignalHandler)(int signum); extern SignalHandler 信号(int signum,SignalHandler 处理程序);或者,我想问的是,可以用来提出第二个版本的基本概念是什么?连接“SignalHandler”和第一个 typedef 的基础是什么?我认为这里需要解释的是 typedef 在这里实际上是做什么的。谢谢 很好的答案,很高兴我回到了这个帖子。我不认为我明白一切,但总有一天我会的。这就是我喜欢SO的原因。谢谢。 只是挑剔:在信号处理程序中调用 printf() 和朋友是不安全的; printf()是不可重入的(主要是因为它可以调用malloc(),它是不可重入的) @FredOverflow:语法合法,是的;但是,任何使用您建议的常规函数​​名称形式之一的人都应该被挂起、绘制,并在他们的余生中使用 Visual Basic 进行编码。任何使用三颗星符号的人,除了证明它是合法的,同样应该受到谴责。 extern void (*signal(int, void(*)(int)))(int); 表示signal(int, void(*)(int)) 函数将返回指向void f(int) 的函数指针。当您想指定函数指针作为返回值时,语法会变得复杂。您必须将返回值类型放在 left 并将参数列表放在 right,而它是您定义的 middle。而在这种情况下,signal() 函数本身将函数指针作为其参数,这使事情变得更加复杂。好消息是,如果你能读到这篇文章,原力已经与你同在。 :)。【参考方案2】:

函数指针与任何其他指针一样,但它指向函数的地址而不是数据的地址(在堆或堆栈上)。像任何指针一样,它需要正确输入。函数由它们的返回值和它们接受的参数类型定义。所以为了完整地描述一个函数,你必须包括它的返回值和每个参数的类型是接受的。 当你 typedef 这样一个定义时,你给它一个“友好的名字”,这使得使用该定义创建和引用指针变得更加容易。

例如假设你有一个函数:

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

然后是下面的typedef:

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

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

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

pt2Func *myFnPtr = &doMultiplication;

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

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

这很好读:http://www.newty.de/fpt/index.html

【讨论】:

精神病学,谢谢!那很有帮助。指向函数指针网页的链接非常有用。现在阅读。 您可能想要使用pt2Func myFnPtr = &amp;doMultiplication; 而不是pt2Func *myFnPtr = &amp;doMultiplication;,因为myFnPtr 已经是一个指针。 声明 pt2Func *myFnPtr = &doMultiplication;而不是 pt2Func myFnPtr = &doMultiplication;引发警告。 @Tamilselvan 是正确的。 myFunPtr 已经是一个函数指针,所以使用 pt2Func myFnPtr = &amp;doMultiplication; @DustinBiser 我认为ptr2Func myFnPtr = doMultiplication 也会做得很好。 &amp; 并不是必需的。【参考方案3】:

cdecl 是破译函数指针声明等怪异语法的好工具。您也可以使用它来生成它们。

关于使复杂声明更易于解析以供将来维护(由您自己或其他人)的技巧,我建议将typedefs 制作成小块,并将这些小块用作更大和更复杂表达式的构建块。例如:

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 )

而且(事实上)正是我在上面产生了疯狂的混乱。

【讨论】:

嗨,Carl,这是一个非常有见地的例子和解释。另外,感谢您展示了 cdecl 的使用。非常感谢。 windows 有 cdecl 吗? @Jack,我相信你可以构建它,是的。 还有cdecl.org,它提供了相同的功能,但在线。对我们 Windows 开发人员很有用。 在第一次偶然发现这个神奇的工具多年后,我一直在寻找它 - 很高兴随机偶然发现这个答案以重新找到它!【参考方案4】:

理解函数指针的typedef的一个非常简单的方法:

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;

【讨论】:

【参考方案5】:
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\n",c);
  c = substract(11, 5);   //calling function via new variable
  printf("%d",c);
  return 0;

这个输出是:

22

6

请注意,相同的 math_func 定义器已用于声明这两个函数。

extern struct 可以使用 typedef 的相同方法。(在其他文件中使用 sturuct。)

【讨论】:

【参考方案6】:

使用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 专家编程

【讨论】:

【参考方案7】:

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

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

【讨论】:

以上是关于了解 C 中函数指针的 typedef的主要内容,如果未能解决你的问题,请参考以下文章

C ++ typedef函数定义为类成员,以后用于函数指针?

函数指针和typedef的使用

函数指针和typedef的使用

函数指针和typedef的使用

const指针与typedef的交互以及c中的函数声明

typedef struct