当前位置:首页 > 技术学院 > 码农的荒岛求生
[导读]实际上函数指针本质也是一个指针,只不过这个指针指向的不是内存中的一段数据而是内存中的一段代码

大家好,我是小风哥。

C/C++中可以使用指针指向一段代码,这个指针就叫函数指针,假设有这样一段代码:

#include  int func(int a) { return a + 1;
}

void main() {
   int (*f)(int) = func; printf("%p\n", f);
}

我们定义了一个函数func,然后使用指针变量f指向该函数,然后打印出变量f指向的地址,代码很简单,然后我们编译一下,看下编译后生成的指令,我们重点关注func函数:

0000000000400526:
  400526:       55                      push   %rbp
  400527:       48 89 e5                mov    %rsp,%rbp
  40052a:       89 7d fc mov    %edi,-0x4(%rbp)
  40052d:       8b 45 fc mov    -0x4(%rbp),%eax
  400530:       83 c0 01                add $0x1,%eax
  400533:       5d                      pop    %rbp
  400534:       c3                      retq

可以看到,编译好后的函数func位于地址0x400526这个地址,让我们记住这个地址。

然后运行一下编译后生成的程序,想一想这段代码会输出什么呢?

显然应该是func函数的在内存中的地址!

$ ./a.out
0x400526

没有猜错吧,实际上函数指针本质也是一个指针,只不过这个指针指向的不是内存中的一段数据而是内存中的一段代码,就像这样:

看到了吧,我们常说的指针一般都是指向内存中的一段数据,而函数指针指向了内存中的一段代码,在这个示例中指向了内存地址0x400526,在这个地址中保存了函数func的机器指令。

现在你应该明白函数指针了,细心的同学可能会有一个疑问,为什么编译器在生成可执行文件时就知道函数func存放在内存地址0x400526上呢?这不应该是程序被加载到内存后开始运行时才能确定的吗?

这个问题的答案收录在了星球中,星球里的同学查看最新一期的文章即可。

函数指针的作用是可以把一段代码当做一个变量传来传去,主要的用途之一就是回调函数,关于回调函数你可以参考《回调函数的实现原理》这篇文章。

关于回调函数其实是在A模块定义,在B模块被调用,就像这样:


然而有时我们会有这样的场景,我们依然需要在模块A定义函数,同时函数A的运行需要依赖B模块产生的数据,然后将模块A定义的函数和模块B产生的数据一并传递给C模块来调用,就像这样:

此时,单纯的函数指针已经不够用了,因为函数指针只是单纯的指向了内存中的一段代码,我们不但需要将内存中的一段代码同时也需要将内存中的一块数据传递给模块C,此时你可以定义一个结构体,将代码和数据打包起来,就像这样:

typedef void (*func) (int);

struct closure{
  func f;
  int arg;    
};

我们将这个结构体命名为closure,注意看,这个结构中有两部分:

  • 一个指向代码的指针变量
  • 一个保存数据的变量

这样,我们在A模块为指针变量赋值,在B模块为保存数据的变量赋值,然后将此结构体传递给模块C,模块C中可以这样使用:

void run(struct functor func) {
    func->f(func->arg);
}

即,closure既包含了一段代码也包含了这段代码使用的数据,这里的数据也被称为context,即上下文,或者environment,即环境,不管怎么称呼,其实就是函数运行依赖的数据:

而这也正是C++中std::function的目的所在。

单纯的函数指针并没有捕捉上下文的能力,这里的上下文就是指代码依赖的数据,你不得不自己动手构造出一个结构体用来存储代码依赖的上下文。

在C++中你没有办法单纯的利用函数指针指向对象的成员函数,就是因为函数指针没有办法捕捉this(指向对象的指针)这个上下文。

std::function的作用本质上和我们刚才定义的结构体区别不大。

利用std::function你不但可以保存一段代码,同时也可以保存必要的上下文,然后在合适的地方基于上下文调用这段代码。

同时std::function也更加通用,你可以用其存储任何可以被调用的对象(callable object),只要有正确的函数签名即可。

最后,我准备开通知识星球啦,鼓励大家在这里输出自己的深度、系统性的思考,沉淀出知识,我个人的力量毕竟有限,不可能把计算机中所有的主题都能系统性总结出来,但有了你们就不一样了。
加入星球,你既可以选出成为和小风哥一样的输出者(写作),也可以只作为输入者(阅读),并努力进化成为我们希望的样子。
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭