函数指针是C语言指针中的一个分支:函数指针是指向函数地址的指针。和一般的指针一样,函数指针可以大大增强编程时的灵活性。这篇博文根据我的理解,简单介绍了自己对于函数指针的理解。
在介绍函数指针之前,我们先来理解一下究竟什么是函数。以下一段代码定义了名为fun1(),接受int并返回int参数的函数:
int fun1(int x) { // fun1是函数名 return x + 1; // 函数体 }
从一般的理解角度来看,fun1是函数的函数名。之所以要定义函数名,是因为在程序的其他位置要调用该函数时,可以直接使用这个别名。这种使用别名的方法和变量相似,但又不完全一样。对一个变量x而言,其意义如下:
物理地址 | 数值 | 说明 |
0x20000000 | 30 | <-- x是该数值单元的别名 |
x是内存单元0x20000000所对应的内存单元。此处x = 30,而x的地址&x = 0x20000000.
那么对一个函数而言,函数名又代表什么呢?假设之前的fun1函数在编译后被放置在0x08000200地址,则fun1对应的内存结构如下所示:
物理地址 | 数值 | 说明 |
fun1 (0x08000200) | fun1_entry | <-- fun1是函数的入口地址 |
在这里,函数名代表一个记录了函数入口地址的存储单元的物理地址。可见,函数名的本质是地址。在编译阶段,函数名被转化成为对应的地址。在使用 xxxx() 函数调用的语法时,该地址被载入程序计数器PC,函数参数及当前现场被弹入堆栈。最后进行函数的实际跳转和执行。
对于一般变量而言,指针可以指向变量的地址,并修改变量的内容:
物理地址 | 数值 | 说明 |
0x20000000 | 30 | <-- x是整形变量 |
0x20000004 | 0x20000000 | <-- p是指向x的指针 |
这里p是指向x的指针,(即p = &x)。此时p的内存单元所存储的是x单元的物理地址,通过 *p 解析地址之后就可以访问或修改x单元的内容。同样的,函数也有其对应的指针 - 函数指针。函数指针是一种特殊的指针,其指向的对象不是变量而是函数。函数指针指向目标函数的入口地址(首地址)。这里我们定义一个指向fun1函数的函数指针pf:
int fun1(int); // fun1是一个(含int输入参数和int返回参数的)函数 int (*pf)(int); // pf是(指向返回int型,含int参数函数的)函数指针 pf = &fun1; /* 也可以直接写成: */ int (*pf)(int) = &fun1; /* 如果需要调用函数指针对应的函数,可以写为:*/ (*pf)(); // 等价于fun1()
到这里,我们回顾上一节中说到的函数名。其实函数名也可以理解成为const型的函数指针。所以在c语言中,以下调用也是合法的:
(*fun1)() // 等价于fun1() pf = fun1 // 等价于pf = &fun1; /* 但是fun1的值不能被修改 */ fun1 = fun2 // 错误!fun1是const类型的指针
函数指针的声明较为冗长,如果需要定义多个同类型的函数指针。可以通过typedef定义一个函数指针类型,从而进行简化:
typedef int (*PF)(int); // 声明PF是一个函数指针类型 PF pf1 = fun1; PF pf2 = fun2;
以上说明了如何定义函数指针,下面介绍函数指针的应用场景。函数指针最常见的应用还是作为回调函数的参数。一般在事件驱动的程序框架中,当对应事件发生时,需要触发对应的处理函数。以下代码实现了在初始化阶段,将事件与对应的处理函数关联(假设set_event_callback()是实现该功能的系统函数):
typedef int (*CALLBACK)(int); #define EVENT_1 (0x01) #define EVENT_2 (0x02) int set_event_callback(const int e, CALLBACK); void init_callbacks() { CALLBACK pf1, pf2; set_event_callback(EVENT_1, pf1); set_event_callback(EVENT_2, pf2); }
在set_event_callback中,函数指针pf作为参数传递给函数使用。在基于事件编程的框架中(如一般的GUI库),函数指针经常以作为回调函数的方式出现。函数指针的另一个应用就是,根据当前程序进程的不同,要在不同条件下调用不同的处理函数:
void change_function(int nEvent, PF *ppf) { switch (nEvent) { case 0: *ppf = fun1; break; case 1: *ppf = fun2; break; default: *ppf = fun3; } }
这里利用了函数指针指向的函数是可以改变的(而非函数名是const型的)。change_function() 将函数指针的地址(指向函数指针的指针)作为参数传入,并根据当前nEvent的情况更改原函数指针指向的函数。
函数指针的另一个应用场景,是允许程序在只知道函数物理地址(但不知道具体的函数名)的情况下进行函数跳转。相信大家都还记得这个经典的C语言面试题:
(*(void(*)(void))0)(); // 或 ((void(*)(void))0)();
此语句的本质就是将0内存位置强制转换成为了函数指针,并调用了该函数指针指向的函数。以下代码解释了此处是如何将0地址转换成函数指针的:
typedef void (*PF)(void); // (*(void(*)(void))0)() 等价于 (*(PF)0)() // 或 ((PF)0)()
函数指针的更高级用法就是组成函数指针数组,或和指向函数指针的指针配合使用。在此不再继续介绍,感兴趣的读者可自行研究。
[1] Kenneth A.Reek, C和指针(第二版), 2008, 人民邮电出版社
[2] Brian W. Kernighan / Dennis M. Ritchie, The C Programming Language (Second Edition), 1989, Prentice Hall
[3] C语言中文网,C语言函数名与函数指针详解,http://c.biancheng.net/cpp/html/496.html