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

所以我想知道它们是如何工作的。 为了解释我的意思是“函数调用者”,我的意思是glutTimerFunc一个很好的例子,它能够接受一个函数作为参数并调用它,即使它不知道它被声明。 它是怎么做到的?

您所谈论的内容称为回调,并使用C和C ++中的函数指针实现。

既然你提到了过剩,那么让我们直接从freeglut源代码中获取一个真实的例子。 我将使用glutIdleFunc而不是glutTimerFunc,因为代码更简单。

在glut中,idle函数回调(你提供给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类型的变量,我们建立的是特定函数指针的另一个名称。 您可以将IdleCallback字段设置为指向用户使用glutIdleFunc函数提供的函数的地址。 该函数的(简化)定义是:

 void glutIdleFunc( void (* callback)( void ) ) { fgState.IdleCallback = callback; } 

这里,fgState是一个SFG_State变量。 如您所见,glutIdleFunc接受一个参数,该参数是一个函数指针,该函数指向不带参数且不返回任何内容的函数,此参数的名称为回调函数。 在函数内部,全局fgState变量中的IdleCallback设置为用户提供的回调。 当你调用glutIdleFunc函数时,你传递了你自己的函数的名字(例如glutIdleFunc(myIdle)),但你真正传递的是函数的地址。

之后,在由glutMainLoop发起的大过量事件处理循环中,您将找到以下代码:

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

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

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

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

在编译的代码中,函数只不过是CPU可以向量执行的地址。 传递函数指针时,编译器(以及稍后的链接器)会在调用中插入正确的地址。

函数参数必须完全匹配,因为执行代码将简单地推送堆栈上的值(或寄存器,具体取决于体系结构),并期望弹出(读出)返回值。