首页 文章

理解C中函数指针的typedef

提问于
浏览
196

当我读到其他人的代码时,我总是有点难过,这些代码的typedef用于指向带参数的函数的指针 . 我记得在尝试理解用C语言编写的数值算法时,我花了一些时间来解决这个定义 . 那么,您是否可以分享您关于如何编写好的typedef指向函数(Do和Do)的提示和想法,为什么它们有用以及如何理解其他人的工作?谢谢!

7 回答

  • 262

    考虑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); //

    typedef void(* SignalHandler)(int signum);
    extern SignalHandler信号(int signum,SignalHandler处理程序);
    或者,我想问的是,你可以使用什么基础概念来提出你的第二个版本?连接“SignalHandler”和第一个typedef的基础是什么?我认为这里需要解释的是typedef实际上在这里做什么 .

    让我们再试一次 . 第一个是直接从C标准中取出 - 我重新输入它,并检查我的括号是否正确(直到我纠正它 - 这是一个难以记住的坚果 Cookies ) .

    首先,请记住 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个标准头文件以使代码编译干净所需?

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

    然后我创建一个结构数组,其中每个元素标识一个信号编号和要为该信号安装的处理程序 . 我经常担心 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() 的两个声明如何相互关联 .

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

    extern SignalHandler signal(int signum, SignalHandler handler);
    

    如果我们更改了函数名称和类型如下:

    extern double function(int num1, double num2);
    

    你将没有问题解释为一个函数,它将一个 int 和一个 double 作为参数并返回一个 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年或者说,它将成为你的第二天性(如果你聪明的话,甚至可能更快) .

  • 23

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

    例如,假设您有一个功能:

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

    然后是以下typedef:

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

    可以用来指向这个 doMulitplication 函数 . 它只是定义一个指向函数的指针,该函数返回一个float并接受两个参数,每个参数都是float类型 . 该定义的友好名称为 pt2Func . 注意 pt2Func 可以指向ANY函数,该函数返回一个浮点数并接收2个浮点数 .

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

    pt2Func *myFnPtr = &doMultiplication;
    

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

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

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

  • 11

    一种非常简单的方法来理解函数指针的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;
    }
    
  • 3

    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 )
    

    并且(事实上)正是我如何在上面产生那种疯狂的混乱 .

  • 27
    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定义器已用于声明该函数 .

    typedef的相同方法可用于extern结构 . (在其他文件中使用sturuct . )

  • 2

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

    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));
        }
    
  • 63

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

相关问题