理解C语言中函数指针的类型定义

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

307291 次浏览

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

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

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 )

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

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

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

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

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

如果你这样写:

typedef void (*SignalHandler)(int signum);

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

extern  SignalHandler signal(int signum, SignalHandler handler);

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

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

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

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

现代语法使用just:

functionpointer(arg1, arg2, ...);

我可以理解为什么这样做是有效的——我只是更愿意知道我需要寻找变量初始化的位置,而不是一个名为functionpointer的函数。


山姆说:

我以前见过这种解释。然后,就像现在的情况一样,我想我没有理解的是这两种说法之间的联系:

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


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

或者,我想问的是,我们可以用什么基本概念来提出第二个版本?连接“信号处理器”的基本原理是什么?第一个类型定义呢?我认为这里需要解释的是typedef实际上在这里做什么。

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

首先,要记住typedef为类型引入了别名。因此,别名是SignalHandler,它的类型是:

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

“不返回任何内容”部分拼写为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作为信号号,因为相同的函数用于两者。

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

main()函数遍历要安装的处理程序列表。对于每个处理程序,它首先调用signal()来确定进程当前是否忽略该信号,在此过程中,将SIG_IGN安装为处理程序,以确保该信号始终被忽略。如果信号之前没有被忽略,那么它会再次调用signal(),这一次是安装首选的信号处理程序。(另一个值可能是__abc4,该信号的默认信号处理程序。)因为对'signal()'的第一次调用将处理程序设置为SIG_IGN,而signal()返回之前的错误处理程序,因此if语句之后的old的值必须为SIG_IGN——因此断言。(好吧,如果发生了严重的错误,它可能是signal()0 -但然后我会从断言触发中了解到这一点。)

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

注意,函数名可以被视为指向适当类型的函数的指针。当您不应用函数调用括号时(例如在初始化式中),函数名将成为函数指针。这也是为什么通过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);是带有参数-1alarm_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:

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;
}
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 struct。(在其他文件中使用struct)

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

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

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

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

#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:我做这个通用用途,不确定它是否可以修改为特定的功能使用。