函数指针
一、函数定义及调用语法
(1) 函数定义语法:在 C 语言中,通过函数声明来告知编译器函数的名称、返 回类型和参数,通过函数定义来提供函数的主体代码。由于函数定义包含了函数 声明的函数原型,所以我们只对函数定义进行解析即可获取函数的完整信息。函 数定义的语法如下:
- 其中[modifier]是函数的修饰符,不同的修饰符告知编译器对该函数采取何种 特殊的处理,修饰符不是必须的,当对编写的函数有特殊要求时可以根据需要加上相应的修饰符。例如,在 Linux 源码中通常使用“static”关键字来限定某函数 的作用域仅在该文件,不能被外部文件引用。
- <return_type>是函数的返回类型, 一个函数可以返回该类型的一个值,其类型可以是基本类型也可以是自定义的类 型。
- <function_name> 是函数的函数名称,只能使用下划线、大小写字母和数字命名 函数,且不能使用数字作为函数名开头,同时也不能和 C 语言的关键字同名,如 int、double 等。在 C 语言全局作用域中不允许出现同名函数,而函数定义默认是 全局作用域的,在 Linux 这种大型项目中,为防止不同文件的函数名相同,都会 给只在当前文件使用的函数加上 static修饰符,使不同文件中即使出现了同名函数 也不会冲突。
- [parameter_list]是函数参数列表,其可以为空也可以包含若干个参数, 每个参数都由类型和参数名占位符组成,多个参数之间用“,”隔开,如“int a, double b”。
Linux 源码中函数定义的实例如图:
二、函数指针
C 语言中函数指针的存在,使函数的调用关系变得复杂,部分函数调用关系 不能通过函数名直接确定,而是通过函数指针隐式确定的。Linux 内核 C 源码中 有许多使用函数指针的代码,所以需要对函数指针的定义语法和使用方法进行总 结,为提取准确完整的函数调用关系提供理论基础。函数指针的定义语法和使用 方法如下:
2.1、函数指针的定义语法
函数指针的实质是一个指针类型的变量,保存的是 指向的函数的地址,在 C 语言中,函数名就是一个函数的起始地址。但与其他指 针变量不同的是,函数指针定义时需要指出该指针可以指向具有何种返回类型与 参数列表的函数,其具体语法如下:
- 其中,<return_type>表示该函数指针可指向函数的返回类型(比如,int、 void 等)
- <function_pointer_name>是函数指针的名称
- [parameter_list]表示该函数 指针可指向函数的参数类型列表,其中参数类型可以为空也可以包含若干个,每 一个参数类型之间用“,”隔开。
函数指针中的参数类型列表只需要指定参数类型, 而不需要像函数定义时给定每一个参数的形参名。函数指针只能指向与其参数类 型列表中给定的参数个数、顺序、类型和返回类型都完全一致的函数。函数指针 的定义可以作为一条单独的语句使用,也可以作为函数定义时形参列表中的一个 参数。当函数指针作为函数的形参时,函数指针又称为钩子函数,赋值给函数指 针的实参被称为回调函数。
Linux 源码中函数指针的定义实例如图所示, find_inode是一个函数,其参数列表包含四个参数,其中第三个参数为一个函数指 针,该函数指针可以指向返回类型为 int,参数列表为“struct inode *”和“void *” 的函数。
2.2、 函数指针的使用方式
函数指针在使用前需要通过赋值将函数指针与具体 的函数关联起来,赋值方式可分为显式赋值和隐式赋值。
- 显式赋值是指函数指针 像其他变量一样在定义时或定义后通过“=”进行赋值。此时函数指针将指向“=” 之后的函数名,即保存对应函数的首地址。其中在函数指针定义时进行的赋值可 以在函数体内部进行也可以在函数体外部进行,当在函数体外部进行时,函数指 针的作用域是全局的。而在函数指针定义后的赋值只能存在于函数体中,其作用 域仅在该函数体内。
- 隐式赋值是指进行函数调用时,将函数名作为参数传递给函数指针形参,此时函数指针形参就保存了函数名实参的函数首地址。当函数指针 完成赋值之后,函数指针与其指向的函数就建立起了别名关系,函数指针的别名 即其指向的函数名的集合。通过函数指针调用函数和使用函数名调用函数的语法类似:
- 其中<function_pointer_name>为函数指针名
- [argument_list]为传入的实参列表。通过函数指针调用函数后,程序的控制权将交由指向的函数,并将实参赋值给对应的形参,执行完函数体内的代码后,被调用函数将返回一个同返回类型一致的值并将程序控制权交由原函数,程序将从函数调用处继续执行。当一个函数 内部通过函数指针调用了另一个函数时,就构成了该函数与函数指针指向函数的 间接函数调用关系。
2.3、案例
下面将通过 Linux 源码中一个函数指针的使用实例来介绍函数指针的隐式赋 值与间接函数调用关系的形成。
- 在图1中,函数 d_walk的第三个参数定义了一 个名为 enter 的函数指针,其可以指向返回值为 d_walk_ret 类型,参数为 void和 struct dentry的函数。
- 在图2中,函数path_has_submounts调用了d_walk函数, 并将path_check_mount函数作为实参传递给了enter函数指针。
- d_walk中使用enter 函数指针进行函数调用时,实际将会调用图3定义的 path_check_mount 函数, 此时函数 d_walk 和 path_check_mount 之间就间接形成了函数调用关系。