理解C中函数指针的typedef

225 c typedef function-pointers

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

Jon*_*ler 286

考虑signal()C标准的功能:

extern void (*signal(int, void(*)(int)))(int);
Run Code Online (Sandbox Code Playgroud)

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

如果你写:

typedef void (*SignalHandler)(int signum);
Run Code Online (Sandbox Code Playgroud)

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

extern  SignalHandler signal(int signum, SignalHandler handler);
Run Code Online (Sandbox Code Playgroud)

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

不过,这需要一点时间的习惯.不能做的一件事是使用SignalHandler typedef函数定义中的函数编写信号处理函数.

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

(*functionpointer)(arg1, arg2, ...);
Run Code Online (Sandbox Code Playgroud)

现代语法仅使用:

functionpointer(arg1, arg2, ...);
Run Code Online (Sandbox Code Playgroud)

我可以看出为什么会这样 - 我只是想知道我需要查找变量初始化的位置而不是被调用的函数functionpointer.


山姆评论说:

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

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

    typedef void (*SignalHandler)(int signum);
    extern SignalHandler signal(int signum, SignalHandler handler);
Run Code Online (Sandbox Code Playgroud)

或者,我想问的是,你可以使用什么基础概念来提出你的第二个版本?连接"SignalHandler"和第一个typedef的基础是什么?我认为这里需要解释的是typedef实际上在这里做什么.

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

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

指向函数的指针,该函数将整数作为参数并且不返回任何内容.

'没有回报'部分是拼写的void; 作为整数的参数是(我相信)不言自明.以下表示法简单地(或不是)C如何指定函数获取指定的参数并返回给定类型:

type (*function)(argtypes);
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

请注意如何避免printf()在信号处理程序中使用?

那么,我们在这里做了什么 - 除了省略4个标准头文件以使代码编译干净所需?

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

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

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

然后程序会正常运行并退出.

请注意,函数的名称可以视为指向适当类型的函数的指针.如果不应用函数调用括号 - 例如在初始值设定项中 - 函数名称将成为函数指针.这也是通过pointertofunction(arg1, arg2)符号调用函数是合理的原因; 当你看到alarm_handler(1),你可以认为这alarm_handler是一个指向函数的指针,因此alarm_handler(1)是通过函数指针调用函数.

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

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

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

 extern SignalHandler signal(int signum, SignalHandler handler);
Run Code Online (Sandbox Code Playgroud)

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

 extern double function(int num1, double num2);
Run Code Online (Sandbox Code Playgroud)

你将没有问题解释为一个函数,它接受一个int和一个double参数并返回一个double值(如果这有问题,你可能最好不要错过 - 但也许你应该谨慎地提问问题如果这是一个问题那么这个.)

现在,doublesignal()函数不是a,而是将a SignalHandler作为其第二个参数,并返回一个作为结果.

这种机制也可以被视为:

extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
Run Code Online (Sandbox Code Playgroud)

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

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

type var;
Run Code Online (Sandbox Code Playgroud)

然后当你写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
Run Code Online (Sandbox Code Playgroud)

在标准中,typedef被视为语法中的一个存储的类,而不是像staticextern是存储类.

typedef void (*SignalHandler)(int signum);
Run Code Online (Sandbox Code Playgroud)

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

(*alarm_handler)(-1);
Run Code Online (Sandbox Code Playgroud)

结果type void- 没有结果.并且(*alarm_handler)(-1);是一个alarm_handler()带参数的调用-1.

所以,如果我们宣布:

extern SignalHandler alt_signal(void);
Run Code Online (Sandbox Code Playgroud)

这意味着:

(*alt_signal)();
Run Code Online (Sandbox Code Playgroud)

表示空值.因此:

extern void (*alt_signal(void))(int signum);
Run Code Online (Sandbox Code Playgroud)

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

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

extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
Run Code Online (Sandbox Code Playgroud)

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

  • 很好的答案,我很高兴我回到这个主题.我不认为我理解一切,但有一天我会.这就是我喜欢SO的原因.谢谢. (6认同)
  • 我以前见过这个解释.然后,就像现在的情况一样,我认为我没有得到的是两个语句之间的联系:extern void(*signal(int,void(*)(int)))(int);/*和*/typedef void(*SignalHandler)(int signum); extern SignalHandler信号(int signum,SignalHandler处理程序); 或者,我想问的是,你可以使用什么基础概念来提出你的第二个版本?连接"SignalHandler"和第一个typedef的基础是什么?我认为这里需要解释的是typedef实际上在这里做什么.谢谢 (3认同)
  • 只是为了挑选一个:在信号处理程序中调用printf()和朋友是不安全的; printf()不是可重入的(主要是因为它可以调用malloc(),它不是可重入的) (2认同)
  • @FredOverflow:语法上合法,是的;但是任何使用您建议的常规函数​​名称形式之一的任何人都应该在他们的余生中使用 Visual Basic 进行挂起、绘制和编写代码。任何使用三颗星符号的人除了证明它是合法的外,也应该受到同样的谴责。 (2认同)
  • `extern void(*signal(int,void(*)(int)))(int);`表示`signal(int,void(*)(int))`函数将返回一个函数指针`void f (INT)`.如果要将**函数指针指定为返回值**,则语法会变得复杂.您必须将返回值类型放置到**左**,将参数列表放置到**右**,而它是您定义的**中间**.在这种情况下,`signal()`函数本身将函数指针作为其参数,这使事情变得更加复杂.好消息是,如果你能读到这个,*部队已经和你在一起了.*:). (2认同)
  • +1 表示“`typedef` 在语法中被视为存储类,就像 `static` 和 `extern` 一样”。我从来没有注意到那种统一的设计,看到它被指出是非常有帮助的。本质上,您是在声明一个变量,但该变量的名称被用作新类型名称,而不是实际创建变量。由于许多 typedef 的形式为“typedef int new_type_t”,因此很容易假设语义只是“typedef some_type new_name”,即使对于函数指针类型也是如此。现在更容易理解为什么函数指针 typedef 仍然是“中出”。 (2认同)

psy*_*tik 74

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

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

float doMultiplication (float num1, float num2 ) {
    return num1 * num2; }
Run Code Online (Sandbox Code Playgroud)

然后是以下typedef:

typedef float(*pt2Func)(float, float);
Run Code Online (Sandbox Code Playgroud)

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

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

pt2Func *myFnPtr = &doMultiplication;
Run Code Online (Sandbox Code Playgroud)

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

float result = (*myFnPtr)(2.0, 5.1);
Run Code Online (Sandbox Code Playgroud)

这样可以很好地阅读:http://www.newty.de/fpt/index.html

  • 你可能想做`pt2Func myFnPtr =&doMultiplication;`而不是'pt2Func*myFnPtr =&doMultiplication;`因为`myFnPtr`已经是一个指针. (10认同)
  • @Tamilselvan是对的.`myFunPtr`已经是一个函数指针所以使用`pt2Func myFnPtr =&doMultiplication;` (2认同)

小智 31

一种非常简单的方法来理解函数指针的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;
}
Run Code Online (Sandbox Code Playgroud)


Car*_*rum 29

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

至于使复杂声明更容易解析以供将来维护的提示(由您自己或其他人),我建议typedef使用小块并使用这些小块作为更大和更复杂表达式的构建块.例如:

typedef int (*FUNC_TYPE_1)(void);
typedef double (*FUNC_TYPE_2)(void);
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2);
Run Code Online (Sandbox Code Playgroud)

而不是:

typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void);
Run Code Online (Sandbox Code Playgroud)

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 )
Run Code Online (Sandbox Code Playgroud)

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

  • 嗨卡尔,这是一个非常有见地的例子和解释.另外,感谢您展示cdecl的使用.非常感激. (2认同)
  • 还有http://cdecl.org/,提供相同的功能,但在线.对我们Windows开发人员有用. (2认同)

Har*_*ain 12

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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

22

6

请注意,同样的math_func定义器已用于声明该函数.

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


小智 7

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

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

    typedef  int (*action_handler_t)(void *ctx, void *data);
Run Code Online (Sandbox Code Playgroud)

现在我们已经定义了一个名为的类型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;
Run Code Online (Sandbox Code Playgroud)

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

我所有的事件处理程序现在都应该遵守由 action_handler

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

    int handle_event_b(void *fsm_ctx, void *in_msg );
Run Code Online (Sandbox Code Playgroud)

参考:

Linden 的专家 C 编程