上一篇<一起学习C语言:函数(三)> 中,我们了解了变量的储存类别与声明方式,以及函数的递归调用原理。本章节,我们分析函数的指针调用,以及函数指针作函数参数使用的场景。
章节预览:
8. 函数的指针调用
8.1 函数指针作函数参数使用
9. 本章总结
目录预览
章节内容:
8. 函数的指针调用
前面小节中我们了解到全局变量和静态变量在程序编译期间确定逻辑内存地址和内存空间大小,并在程序执行期间分配实际内存地址和对应的内存空间大小。当然,函数也是采用类似的形式,接下来分析函数的加载和执行过程:
在程序编译的过程中,编译器为函数标记一段地址(逻辑地址),这段地址的首地址是函数首地址,并且记录着函数的执行过程以及占用的内存空间大小。
在程序执行的过程中,首先为所有用到的函数分配实际地址(只为动态加载的函数分配实际地址,而静态加载的函数在链接时已经确定实际地址(程序执行时分配这个固定地址)),并在之后调用这个函数时把实际地址压入栈中(在栈中分配函数执行期间需要的内存空间),当函数执行完成后(回收函数执行期间分配的内存)弹出栈。其中,函数的实际地址一直保存到进程退出前释放。
现在我们了解了函数同样具有地址的概念,接下来通过一个示例了解函数调用的过程。
【例8.9】 通过函数地址执行函数:
int func(int a) {
return a;
}
int main() {
void *pFunc = func;
int res = ((int (*)(int))pFunc)(1);
printf(“main res:%d\n”, res);
return 0;
}
示例结果:
main res:1
程序分析:
这个示例中,pFunc首先得到func函数的偏移地址(实际地址),然后通过偏移地址在栈中执行func函数并返回int返回值,最后通过printf输出返回值信息。
C语言中,函数地址可以通过长整形保存,如 long pFunc = (long)func;,但不建议用整形(int)保存。因为在32位编译器中地址占4个字节,在64位编译器中地址占8个字节,而整形在32或64位编译器中都占用4字节,并不能满足64位编译器中的地址存储条件。
另外,程序中需要保存某个函数地址时,可以通过函数指针来储存。
函数指针定义形式:
返回类型 (*函数名称)(参数列表);
函数指针定义举例:
int (*Func)(int);
【例8.10】 通过函数指针执行函数:
int (*Func)(int);
int func(int a) {
return a;
}
int main() {
Func = func; //为函数指针赋实际调用函数的地址
int res = Func(1); //函数指针执行
printf(“main res:%d\n”, res);
return 0;
}
示例结果:
main res:1
使用函数指针需要注意几点:
1. 函数指针名称必须写在小括号内,如(*Func),*写在函数名左侧;
2. 函数指针只是以抽象形式存在,参数为实际调用函数名称;
3. 函数指针传入的参数应与函数指针原型完全匹配。
8.1 函数指针作函数参数使用
函数指针不仅可以调用函数,也可以作为函数参数使用。比如实现一个算法函数接口,在不清楚函数内应该用哪种实现方式的情况下,可以把函数指针作为参数传入,在传入的实际函数内编写实现方式。
【例8.10】 函数指针作函数参数:
int FUNC(int i, int j, int (*multiplication)(int i, int j)) {
return multiplication(i, j);
}
int multiplication(int i, int j)
{
if (9 >= i) {
if (9 >= j) {
printf("%d*%d=%2d “, i, j, i * j);
multiplication(i, ++j);
}
else {
++i;
j = i;
printf(”\n");
multiplication(i, j);
}
}
return 0;
}
int main() {
int res = FUNC(1, 1, multiplication);
printf(“main res:%d\n”, res);
}
示例结果:
接下来,我们把实现函数修改为乘法运算:
int FUNC(int i, int j, int (*multiplication)(int i, int j)) {
return multiplication(i, j);
}
int mul(int i, int j) {
return i * j;
}
int main() {
int res = FUNC(2, 3, mul);
printf(“main res:%d\n”, res);
}
示例结果:
main res:6
当然,函数指针的用处不仅如此,在调用系统函数或第三方库函数时,可以通过函数指针提供函数实现。比如linux内核中不同厂家提供的驱动函数实现方式基本不相同,如果把所有厂家的驱动代码都植入linux内核会让内核体积变得异常庞大,而内核方面只需把相关函数原型以抽象形式提供,不同厂家只需实现自己的驱动函数即可,这样便可以减少内核体积。
9. 本章总结
本章节,我们了解了程序编译的过程以及程序执行时的不同内存应用场景。通过上述示例可以了解到,不同存储类别的变量生命周期或作用域也会不同,函数也可以像指针变量那样调用。在日常编程中,栈内存溢出时基本可以通过代码分析出原因。
目录预览
<一起学习C语言:C语言发展历程以及定制学习计划>
<一起学习C语言:初步进入编程世界(一)>
<一起学习C语言:初步进入编程世界(二)>
<一起学习C语言:初步进入编程世界(三)>
<一起学习C语言:C语言数据类型(一)>
<一起学习C语言:C语言数据类型(二)>
<一起学习C语言:C语言数据类型(三)>
<一起学习C语言:C语言基本语法(一)>
<一起学习C语言:C语言基本语法(二)>
<一起学习C语言:C语言基本语法(三)>
<一起学习C语言:C语言基本语法(四)>
<一起学习C语言:C语言基本语法(五)>
<一起学习C语言:C语言循环结构(一)>
<一起学习C语言:C语言循环结构(二)>
<一起学习C语言:C语言循环结构(三)>
<一起学习C语言:数组(一)>
<一起学习C语言:数组(二)>
<一起学习C语言:数组(三)>
<一起学习C语言:初谈指针(一)>
<一起学习C语言:初谈指针(二)>
<一起学习C语言:初谈指针(三)>
<一起学习C语言:函数(一)>
<一起学习C语言:函数(二)>
<一起学习C语言:函数(三)>