程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。
声明函数指针
回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:
void f();// 函数原型
上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:
void (*) ();
让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:
// 获得函数指针的大小
unsigned psize = sizeof (void (*) ());
// 为函数指针声明类型定义
typedef void (*pfv) ();
pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名可以隐藏复杂的函数指针语法。
指针变量应该有一个变量名:
void (*p) (); //p是指向某函数的指针
p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:
void func()
{
/* do something */
}
p = func;
p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。
传递回调函数的地址给调用者
现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:
void caller(void(*ptr)())
{
ptr(); /* 调用ptr指向的函数 */
}
void func();
int main()
{
p = func;
caller(p); /* 传递函数地址到调用者 */
}
如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。
调用规范
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:
// 被调用函数是以int为参数,以int为返回值
__stdcall int callee(int);
// 调用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int));
// 在p中企图存储被调用函数地址的非法操作
__cdecl int(*p)(int) = callee; // 出错
指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列。
typedef void( *intfunc )( void ); // 定义一个新类型,这个类型是一个函数指针,指针指向的函数类型是void (void)型
typedef union { intfunc __fun; void * __ptr; } intvec_elem;
// 定义一个新类型,这个类型是一个联合体,联合体有两个元素,一个元素类型是intfunc,一个元素类型是void *
//符合标准C
#pragma language=extended
#pragma segment="CSTACK"
void __iar_program_start( void );
// 申明外部函数,extern可以用,也可以不用
#pragma location = ".intvec" // 下面的变量存放在段 .intvec
/* STM32F10x Vector Table entries */
const intvec_elem __vector_table[] =
{
{ .__ptr = __sfe( "CSTACK" ) },
// { .__ptr = 常量 },
// 数组元素是联合体,联合体有两个元素,对联合体元素__ptr赋初值
//符合标准C
//__sfe( "CSTACK" ) 是段CSTACK的尾地址,更准确是段CSTACK下一个段的首地址(记忆中应该是这样)。
//IAR扩展
__iar_program_start,
//数组元素是联合体,联合体有两个元素,对联合体第一个元素__fun赋初值
//完整的写法是 { .__fun= __iar_program_start },
//这里简写成__iar_program_start,是否符合标准C,有待考证
.....
}
CSTACK
Description Holds the internal data stack.
Segment memory type DATA
Memory placement This segment can be placed anywhere in memory.
Access type Read/write
See also The stack, page 31.
这个段是内部数据堆栈,可以放在存储器的任何位置
INTVEC
Description Holds the reset vector and exceptions vectors which contain branch instructions to
cstartup, interrupt service routines etc.
Segment memory type CODE
Memory placement Must be placed at address range 0x00 to 0x3F.
Access type Read-only
这个是中断向量段,放置在地址的范围是0x00到0x3F
估计这个段是只能读
文章评论(0条评论)
登录后参与讨论