C语言中的函数调用者(回调函数)?

3

我在想它们是如何工作的。要解释我所说的“函数调用器”,一个很好的例子就是glutTimerFunc,它能够将函数作为参数传入并调用它,即使它不知道该函数是否已声明。它是如何做到这一点的?

3个回答

6
您所说的是 回调函数(callback),在C和C++中使用函数指针(function pointers)实现。
既然您提到了glut, 我们可以从 freeglut 的源代码中直接拿一个真实的例子。我会使用 glutIdleFunc而不是 glutTimerFunc,因为这段代码更简单。
在glut中,闲置函数回调(指向glutIdleFunc的函数)是一个指向不带参数并且不返回任何东西的函数的指针。 使用typedef给此类函数类型命名:
typedef void (* FGCBIdle)( void );

在这里,FGCBIdle(即FreeGlut CallBack Idle的缩写)被定义为一个不带参数且返回类型为void的函数指针。这只是一种类型定义,使表达式的编写更加容易,它不会分配任何存储空间。

Freeglut有一个叫做SFG_State的结构体,保存着各种设置。该结构体的定义部分如下:

struct tagSFG_State
{
    /* stuff */
    FGCBIdle         IdleCallback;         /* The global idle callback       */
    /* stuff */
};

该结构体包含一个类型为FGCBIdle的变量,我们已经确定它是特定函数指针的另一个名称。您可以使用glutIdleFunc函数将IdleCallback字段设置为指向由用户提供的函数的地址。该函数的(简化)定义如下:
void glutIdleFunc( void (* callback)( void ) )
{
    fgState.IdleCallback = callback;
}

在这里,fgState是一个SFG_State变量。你可以看到,glutIdleFunc接受一个参数,这个参数是指向一个不带参数且无返回值的函数的函数指针,这个参数的名称是callback。在函数内部,全局的fgState变量中的IdleCallback被设置为用户提供的回调函数。当你调用glutIdleFunc函数时,你传递了自己函数的名称(例如glutIdleFunc(myIdle)),但实际上你传递的是函数的地址。

稍后,在由glutMainLoop启动的大型glut事件处理循环中,你会找到以下代码:

if( fgState.IdleCallback )
{
            /* stuff */
            fgState.IdleCallback( );
}

如果用户提供了空闲回调,则在循环中调用它。如果您查看我帖子开头的函数指针教程,您将更好地理解语法,但我希望一般概念现在更有意义。

2

该参数是指向函数的指针。调用者有责任确保函数声明与要求相匹配(例如,参数的数量和类型,调用约定)。


0

作为参数传递的函数被传递为函数指针。

在编译后的代码中,函数只是一个地址,CPU可以通过该地址进行向量执行。当您传递函数指针时,编译器(以及后来的链接器)会将正确的地址插入调用中。

函数参数必须完全匹配,因为执行代码只会将值推送到堆栈上(或寄存器,取决于架构),并期望弹出(读取)返回值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接