C函数指针的理解与使用

通过一些具体例子,来深入理解并掌握函数指针的运用。

C语言函数指针的理解与使用

函数指针的定义

函数指针就是函数的指针。它是一个指针,指向一个函数。看例子:

1
2
3
A) char* (*fun1)(char* p1, char* p2);
B) char** fun2(char* p1, char* p2);
C) char* fun3(char* p1, char* p2);

上面三个表达式分别什么意思?

    1. 这很容易,fun3是函数名,p1,p2是参数,其类型为 char* 型,函数的返回值为 char* 类型。
    1. 也很简单,与C)表达式相比,唯一不同的就是函数的返回值类型为 char**,是个二级指针。
    1. fun1是函数名吗?回忆一下数组指针的定义:
    1
    int (*)[10] p;

    这里 fun1 不是什么函数名,而是一个指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。同样,我们把这个表达式改写一下:

    1
    char* (*)(char* p1, char* p2) fun1;

函数指针使用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>

char* fun(char* p1, char* p2){
  int i = strcmp(p1,p2);
  if (0 == i) return p1;
  else return p2;
}

int main(){
  char* (*pf)(char* p1, char* p2); //声明一个指向改函数的指针,未初始化
  pf = &fun;
  (*pf) ("aa","bb");
  return 0;
}
  • 赋值方法 给函数指针赋值时,可以用 &fun 或直接用函数名 fun。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。如:

    1
    2
    pf = fun;
    pf = &fun;
  • 调用方法 可以直接使用指向函数的指针调用函数,无须提前解引用:

    1
    2
    3
    4
    //三个等价调用
    pf("aa","bb");
    (*pf) ("aa","bb");
    fun("aa","bb");

*(int*)&p 是什么

1
2
3
4
5
6
7
8
9
10
11
void function(){
printf("Call Function!\n");
}

int main(){
//声明函数指针
void (*p)();
*(int*)&p = (int)function;
(*p)();
return 0;
}

*(int*)&p = (int)function; 表示什么意思?先看这行代码:

1
void (*p)();
  • 这行代码定义了一个指针变量 p,p 指向一个函数,这个函数的参数和返回值都是 void
  • &p 是求指针变量 p 本身的地址,这是一个32位的二进制常数(32位系统)。
  • (int*)&p 表示将地址强制转换成指向 int 类型数据的指针。
  • (int)function 表示将函数的入口地址强制转换成 int 类型的数据。
  • *(int*)&p = (int)function; 表示将函数的入口地址赋值给指针变量 p。
  • 那么 (*p) (); 就是表示对函数的调用。

64位系统:

1
2
3
4
5
6
7
8
9
10
11
void function(){
printf("Call Function!\n");
}

int main(){
//声明函数指针
void (*p)();
*(long long*)&p = (long long)function;
(*p)();
return 0;
}

(*(void(*) ())0) () 是什么

1
(*(void(*) ())0)();

这是《C Traps and Pitfalls》书中的一个例子:

  • 第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
  • 第二步:(void(*) ())0,这是将0强制转换为函数指针类型,0是一个地址,也就是说一个函数存在首地址为0的一段区域内。
  • 第三步:(*(void(*) ())0),这是取0地址开始的一段内存里面的内容,其内容就是保存在首地址为0的一段区域内的函数。
  • 第四步:(*(void(*) ())0) (),这是函数调用。

上面的例子再改写:

1
(*(char**(*) (char**, char**))0) (char**, char**);

使用函数指针的好处

可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期的维护,系统结构更加清晰。

归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。

References

http://www.cnblogs.com/haore147/p/3647262.html

https://www.cnblogs.com/huangzhenxiong/p/7772627.html