1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 函数指针深入探索

函数指针深入探索

时间:2021-06-25 12:19:20

相关推荐

函数指针深入探索

函数指针再探

问题提出

函数指针的本质是什么?函数指针的效率和直接调用一样吗? 为什么?函数指针可以跟模板结合吗?函数指针可以作用于静态函数、成员方法、静态方法、纯虚函数、lamda吗?不能的话有什么方法解决?除了函数指针外还有哪些代码可以实现类似功能?

探索

函数指针本质是什么?

引用维基百科上记录的对函数指针的定义,其上是这样说到的

A function pointer, also called a subroutine pointer or procedure pointer is a pointer that point to a function,

函数是执行特定功能的代码块,是一块可执行的区间,在汇编语言层面,调用函数一般是先将当前的某些信息比如程序计数器和其它信息保存到堆栈,然后再跳转到函数执行的入口地址,完成操作之后,从函数体返回,重新回到当前的主程序中来。

一句话说:

函数指针的本质就是指向一块可执行的代码块的首地址,而函数就是这个可执行代码块的首地址

调用函数直接调用即可,而函数指针调用函数时则需要对其进行解引用,但是两者实现的功能是一样的,只是一个是直接用,一个是间接用

在汇编层面比较两种函数调用的形式,比如有一个函数add,通过前面提到的两种方式调用add这个函数,反汇编并查看其底层调用形式,代码如下

int add(int a, int b){return a + b;}typedef decltype(add) *FP;int main(){// 直接调用函数int c1 = add(1,2);// 通过函数指针调用函数FP p = add;int c2= p(1,2);return 0;}

反汇编汇编代码部分如下,删除其细枝末节,只看两种函数调用的部分,反汇编代码如下

.....# add函数的首地址0000000140001530 <_Z3addii>:140001530: 55 push %rbp140001531: 48 89 e5mov %rsp,%rbp140001534: 89 4d 10mov %ecx,0x10(%rbp)140001537: 89 55 18mov %edx,0x18(%rbp)14000153a: 8b 55 10mov 0x10(%rbp),%edx14000153d: 8b 45 18mov 0x18(%rbp),%eax140001540: 01 d0 add %edx,%eax140001542: 5d pop %rbp140001543: c3 retq# main函数的首地址0000000140001544 <main>:......140001551: ba 02 00 00 00mov $0x2,%edx140001556: b9 01 00 00 00mov $0x1,%ecx14000155b: e8 d0 ff ff ffcallq 140001530 <_Z3addii>140001560: 89 45 fcmov %eax,-0x4(%rbp)140001563: 48 8d 05 c6 ff ff ff lea -0x3a(%rip),%rax # 140001530 <_Z3addii>14000156a: 48 89 45 f0 mov %rax,-0x10(%rbp)14000156e: 48 8b 45 f0 mov -0x10(%rbp),%rax140001572: ba 02 00 00 00mov $0x2,%edx140001577: b9 01 00 00 00mov $0x1,%ecx14000157c: ff d0 callq *%rax.....

从中分析,看到两个汇编代码,函数调用使用的汇编代码为

callq 140001530

而函数指针调用函数的汇编代码为

lea -0x3a(%rip), %rax ;将add函数的首地址放入rax寄存器callq *%rax ;间接调用add,add的地址存放在rax里面

从中可以看出,函数指针调用只是将函数的首地址放入到一个寄存器中,然后间接的跳转到这个位置

函数指针的效率和函数调用的效率相比?

从Wiki百科上查找到对函数指针调用函数性能的描述为

Extensively using function pointers to call functions may produce a slow-down for the code on modern processors, because branch predictor may not be able to figure out where to branch to (it depends on the value of the function pointer at run time) although this effect can be overstated as it is often amply compensated for by significantly reduced non-indexed table lookups.

在现代处理器上,大量使用函数指针来调用函数可能会导致代码速度变慢,因为分支预测器可能无法确定分支到哪里(这取决于运行时函数指针的值),尽管这种效果可能被夸大了,因为它经常被大量减少的非索引表查找所弥补

来自Function pointer - Wikipedia

意思可能是说,两者的性能差别并不是很大

函数可以跟模板结合吗?

可以跟模板结合

#include<stdio.h>template<typename type>type add(type type1, type type2){ return type1 + type2;}void func1(double (*p1)(double, double), double a, double b){printf("The double value %f + %f = %f\n", a, b, p1(a,b));}void func2(int (*p1)(int, int), int a, int b){printf("The integer value %d + %d = %d\n", a, b, p1(a,b));}int main(int argc, char *argv[]){func1(add<double>, 1.0, 2.0);func2(add<int>, 1, 2);return 0;}

D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exeThe double value 1.000000 + 2.000000 = 3.000000The integer value 1 + 2 = 3

函数指针可以指向哪些定义的函数模板

函数指针可以作用在静态函数、成员方法、静态方法、纯虚函数、lamda上面吗,如果不能用什么替代?

作用在静态函数

#include<stdio.h>// 静态函数定义static int add_func(int a, int b){return a + b;}int main(int argc, char *argv[]){typedef int (*calcu_ptr)(int, int); // 定义函数指针类型printf("The func pointer's size is %d\n", int(sizeof(calcu_ptr)));calcu_ptr c = &add_func; // 函数指针指向add_funcprintf("The result of the %d + %d = %d", 1, 2, c(1,2));return 0;}

D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exeThe func pointer's size is 8The result of the 1 + 2 = 3

作用在成员方法

#include<stdio.h>class ALU{// 完成一些基本的运算操作public:int alu_Add(int a, int b){return a + b;}};int main(int argc, char *argv[]){typedef int (ALU::*calc_ptr)(int, int); // 定义指向成员方法的指针calc_ptr c = &ALU::alu_Add; // 指向类的普通成员方法printf("The member func pointer's size is %d\n", int(sizeof (calc_ptr)));ALU *a = new ALU();printf("The result of %d + %d = %d\n", 1, 2,(a->*c)(1,2)); // 普通成员方法的使用return 0;}

D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exeThe member func pointer's size is 16The result of 1 + 2 = 3

指向成员静态方法

#include<stdio.h>class ALU{// 完成一些基本的运算操作public:static int alu_Add(int a, int b){return a + b;}};int main(int argc, char *argv[]){typedef int (*calc_ptr)(int, int); // 定义函数指针calc_ptr c = &ALU::alu_Add; // 指向静态成员方法 printf("The static member func pointer's size is %d\n", int(sizeof (calc_ptr)));printf("The result of %d + %d = %d\n", 1, 2,c(1,2)); // 使用静态成员方法return 0;}

D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exeThe static member func pointer's size is 8The result of 1 + 2 = 3

指向纯虚函数

#include<stdio.h>class Operation{public:virtual int Calcu(int a, int b) = 0;};class Add:public Operation{public:int Calcu(int a, int b) override{ return a + b; }};class Sub:public Operation{public:int Calcu(int a, int b) override{ return a - b; }};int main(int argc, char *argv[]){typedef int (Operation::*Calcu)(int, int);Calcu c = &Operation::Calcu;printf("The virtual member func's size is %d\n", int(sizeof(Calcu)));Add a;Sub s;printf("%d + %d = %d\n", 1, 2, (a.*c)(1,2));printf("%d - %d = %d\n", 2, 1, (s.*c)(2,1));return 0;}

D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exeThe virtual member func's size is 161 + 2 = 32 - 1 = 1

函数指针指向lamda

#include<stdio.h>typedef int (*Calcu)(int, int);int main(){Calcu c = [](int a, int b){return a + b;};printf("The func pointer size is %d\n", int(sizeof (Calcu)));printf("%d + %d = %d\n", 1, 2, c(1,2));return 0;}

D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exeThe func pointer size is 81 + 2 = 3

从上面的代码中可以得到以下结论

函数指针可以作用于函数、成员方法、静态成员方法、纯虚函数、lamda函数指针指向成员方法和纯虚函数时,其大小为普通函数指针的两倍,而且其定义方法有差异,大小为普通函数指针的两倍,这是因为这些方法的执行是要有特定的对象的,函数指针不仅仅要指向这个方法的地址,还要指向这个方法的所有者(对象)的地址。所以其大小要比普通的函数指针大两到三倍

除了函数指针外,还有什么可以实现类似的功能

functor,仿函数,函数说白了就是一个语句块,他能实现一些功能,并且能返回值,这就是一个函数,但是在函数调用的过程中,通过函数执行的功能只能通过传递参数和调用某些全局变量的形式进行处理,关键在于它不具有状态,而次,函数对象就不会这样,它的定义是,函数对象它不仅能实现函数的功能,还能带有自己的状态,如果你的一段代码能实现这样的一个功能,那么他就是一个函数对象,而在C++中,有一种实现函数对象的方式就是重载()运算符

比如说一个自动可乐售货机,它里面只有10罐可乐,用户通过buyCola这个函数来获取可乐,当10罐买完后,就没有可乐了,所以需要给出售罄的提示,这样一个场景,仅仅靠函数是无法实现的,但是通过**函数对象(或者说仿函数functor)**是可以实现的,因为后者可以记录这个状态,请看代码

#include<stdio.h>class BuyCola{public:BuyCola(int cola_count):cola_count(cola_count){}// 重载运算符void operator()(){if(cola_count > 0){printf("You will buy a cola\n");cola_count--;}else{printf("The cola is sold out!\n");}}private:// 可乐数量int cola_count; };int main(){BuyCola vending_maching(10);for (int i = 0; i < 13; ++i){vending_maching();}return 0;}

D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exeYou will buy a colaYou will buy a colaYou will buy a colaYou will buy a colaYou will buy a colaYou will buy a colaYou will buy a colaYou will buy a colaYou will buy a colaYou will buy a colaThe cola is sold out!The cola is sold out!The cola is sold out!

对于具体的仿函数的介绍可以参考Wiki或者是其它的资料,以上仅是个人理解,如有错误,欢迎指出!

下面是Wiki截取的原文

A typical use of a function object is in writing callback functions. A callback in procedural languages, such as C, may be performed by using function pointers.[2] However it can be difficult or awkward to pass a state into or out of the callback function. This restriction also inhibits more dynamic behavior of the function. A function object solves those problems since the function is really a façade for a full object, carrying its own state.

Many modern (and some older) languages, e.g. C++, Eiffel, Groovy, Lisp, Smalltalk, Perl, PHP, Python, Ruby, Scala, and many others, support first-class function objects and may even make significant use of them.[3] Functional programming languages additionally support closures, i.e. first-class functions that can ‘close over’ variables in their surrounding environment at creation time. During compilation, a transformation known as lambda lifting converts the closures into function objects.

Function object - Wikipedia

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。