C 语言函数指针 (Pointers to Functions, Function Pointers)

news2025/1/7 21:30:44

C 语言函数指针 {Pointers to Functions, Function Pointers}

  • 1. Pointers to Functions (函数指针)
  • 2. Function Pointers (函数指针)
    • 2.1. Declaring Function Pointers
    • 2.2. Assigning Function Pointers
    • 2.3. Calling Function Pointers
  • 3. Jump Tables (转移表)
  • References

1. Pointers to Functions (函数指针)

jump tables and passing a function pointer as an argument in a function call
函数指针最常见的两个用途是转换表和作为参数传递给另一个函数。

Like any other pointer, a pointer to a function must be initialized to point to something before indirection can be performed on it.
和其他指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数。

The following code fragment illustrates one way to initialize a pointer to a function.

	int f(int);
	int(*pf)(int) = &f;

The second declaration creates pf, a pointer to a function, and initializes it to point to the function f. The initialization can also be accomplished with an assignment statement.
第 2 个声明创建了函数指针 pf,并把它初始化为指向函数 f。函数指针的初始化也可以通过一条赋值语句来完成。

It is important to have a prototype for f prior to the initialization, for without it the compiler would be unable to check whether the type of f agreed with that of pf.
在函数指针的初始化之前具有 f 的原型是很重要的,否则编译器就无法检查 f 的类型是否与 pf 所指向的类型一致。

The ampersand in the initialization is optional, because the compiler always converts function names to function pointers wherever they are used. The ampersand does explicitly what the compiler would have done implicitly anyway.
初始化表达式中的 & 操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。& 操作符只是显式地说明了编译器将隐式执行的任务。

ampersand /ˈæmpəsænd/:n. &

After the pointer has been declared and initialized, there are three ways to call the function:

	int ans;
	ans = f(25);
	ans = (*pf)(25);
	ans = pf(25);

The first statement simply calls the function f by name, though its evaluation is probably not what you expected. The function name f is first converted to a pointer to the function; the pointer specifies where the function is located. The function call operator then invokes the function by executing the code beginning at this address.
第 1 条语句简单地使用名字调用函数 f,但它的执行过程可能和你想象的不太一样。函数名 f 首先被转换为一个函数指针,该指针指定函数在内存中的位置。然后,函数调用操作符调用该函数,执行开始于这个地址的代码。

The second statement applies indirection to pf, which converts the function pointer to a function name. This conversion is not really necessary, because the compiler converts it back to a pointer before applying the function call operator. Nevertheless, this statement has exactly the same effect as the first one.
第 2 条语句对 pf 执行间接访问操作,它把函数指针转换为一个函数名。这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句的效果和第 1 条语句是完全一样的。

The third statement has the same effect as the first two. Indirection is not needed, because the compiler wants a pointer to the function anyway. This example shows how function pointers are usually used.
第 3 条语句和前两条语句的效果是一样的。间接访问操作并非必需,因为编译器需要的是二个函数指针。这个例子显示了函数指针通常是如何使用的。

The two most common uses of pointers to functions are passing a function pointer as an argument in a function call and jump tables.
两个最常见的用途是把函数指针作为参数传递给函数以及用于转换表。

2. Function Pointers (函数指针)

A function name refers to a fixed function. Sometimes it is useful to call a function to be determined at run time; to do this, you can use a function pointer value that points to the chosen function (see Pointers).
函数名指的是固定函数。

Pointer-to-function types can be used to declare variables and other data, including array elements, structure fields, and union alternatives. They can also be used for function arguments and return values. These types have the peculiarity that they are never converted automatically to void * or vice versa. However, you can do that conversion with a cast.
指向函数的指针类型可用于声明变量和其他数据,包括数组元素、结构字段和联合替代项。它们还可用于函数参数和返回值。这些类型的特性是它们永远不会自动转换为 void * 。但是,你可以使用强制类型转换来完成这种转换。

2.1. Declaring Function Pointers

The declaration of a function pointer variable (or structure field) looks almost like a function declaration, except it has an additional * just before the variable name. Proper nesting requires a pair of parentheses around the two of them. For instance, int (*a) (); says, “Declare a as a pointer such that *a is an int-returning function.”
正确的嵌套需要用一对括号括住它们两个。

Contrast these three declarations:

// Declare a function returning char *
char *a (char *);
// Declare a pointer to a function returning char
char (*a) (char *);
// Declare a pointer to a function returning char *
char *(*a) (char *);

The possible argument types of the function pointed to are the same as in a function declaration. You can write a prototype that specifies all the argument types:

rettype (*function) (arguments…);

or one that specifies some and leaves the rest unspecified:

rettype (*function) (arguments…, ...);

or one that says there are no arguments:

rettype (*function) (void);

You can also write a non-prototype declaration that says nothing about the argument types:

rettype (*function) ();

For example, here’s a declaration for a variable that should point to some arithmetic function that operates on two doubles:

double (*binary_op) (double, double);

Structure fields, union alternatives, and array elements can be function pointers; so can parameter variables. The function pointer declaration construct can also be combined with other operators allowed in declarations. For instance,

int **(*foo)();

declares foo as a pointer to a function that returns type int **, and

int **(*foo[30])();

declares foo as an array of 30 pointers to functions that return type int **.

int **(**foo)();

declares foo as a pointer to a pointer to a function that returns type int **.

2.2. Assigning Function Pointers

Assuming we have declared the variable binary_op as in the previous section, giving it a value requires a suitable function to use. So let’s define a function suitable for the variable to point to. Here’s one:

double double_add(double a, double b) {
	return a + b;
}

Now we can give it a value:

binary_op = double_add;

The target type of the function pointer must be upward compatible with the type of the function.

There is no need for & in front of double_add. Using a function name such as double_add as an expression automatically converts it to the function’s address, with the appropriate function pointer type. However, it is ok to use & if you feel that is clearer:

binary_op = &double_add;

2.3. Calling Function Pointers

To call the function specified by a function pointer, just write the function pointer value in a function call. For instance, here’s a call to the function binary_op points to:

binary_op (x, 5)

Since the data type of binary_op explicitly specifies type double for the arguments, the call converts x and 5 to double.

The call conceptually dereferences the pointer binary_op to “get” the function it points to, and calls that function. If you wish, you can explicitly represent the dereference by writing the * operator:

(*binary_op) (x, 5)

The * reminds people reading the code that binary_op is a function pointer rather than the name of a specific function.
* 提醒阅读代码的人 binary_op 是一个函数指针,而不是特定函数的名称。

3. Jump Tables (转移表)

The following code fragment is from a program that implements a pocket calculator. Other parts of the program have already read in two numbers (op1 and op2) and an operator (oper). This code tests the operator to determine which function to invoke.
下面的代码段取自一个程序,它用于实现一个袖珍式计算器。程序的其他部分已经读入两个数 (op1 and op2) 和一个操作符 (oper)。下面的代码对操作符进行测试,然后决定调用哪个函数。

	switch (oper) {
	case ADD:
		result = add(op1, op2);
		break;
	case SUB:
		result = sub(op1, op2);
		break;
	case MUL:
		result = mul(op1, op2);
		break;
	case DIV:
		result = div(op1, op2);
		break;
    ...
	}

It is good design to separate the operations from the code that chooses among them. The more complex operations will certainly be implemented as separate functions because of their size, but even the simple operations may have side effects, such as saving a constant value for later operations.
把具体操作和选择操作的代码分开是一种良好的设计方案。更为复杂的操作将肯定以独立的函数来实现,因为它们的长度可能很长。但即使是简单的操作也可能具有副作用,例如保存一个常量值用于以后的操作。

In order to use a switch, the codes that represent the operators must be integers. If they are consecutive integers starting with zero, we can use a jump table to accomplish the same thing. A jump table is just an array of pointers to functions.
为了使用 switch 语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针数组。

There are two steps in creating a jump table. First, an array of pointers to functions is declared and initialized. The only trick is to make sure that the prototypes for the functions appear before the array declaration.
创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。

	double add(double, double);
	double sub(double, double);
	double mul(double, double);
	double div(double, double);
	...

	double (*oper_func[])(double, double) = {
		add, sub, mul, div, ...
	};

The proper order for the functionsʹ names in the initializer list is determined by the integer codes used to represent each operator in the program. This example assumes that ADD is zero, SUB is one, MUL is two, and so forth.
初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。这个例子假定 ADD 是 0,SUB 是 1,MUL 是 2,接下去以此类推。

The second step is to replace the entire switch statement with this one!
第 2 个步骤是用下面这条语句替换前面整条 switch 语句。

result = oper_func[oper](op1, op2);

oper selects the correct pointer from the array, and the function call operator executes it.
oper 从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。

An out‐of‐bounds subscript is just as illegal on a jump table as it is on any other array, but it is much more difficult to diagnose.
在转换表中,越界下标引用就像在其他任何数组中一样是不合法的。但一旦出现这种情况,把它诊断出来要困难得多。当这种错误发生时,程序有可能在三个地方终止。首先,如果下标值远远越过了数组的边界,它所标识的位置可能在分配给该程序的内存之外。有些操作系统能检测到这个错误并终止程序,但有些操作系统并不这样做。如果程序被终止,这个错误将在靠近转换表语句的地方被报告,问题相对而言较易诊断。

如果程序并未终止,非法下标所标识的值被提取,处理器跳到该位置。这个不可预测的值可能代表程序中一个有效的地址,但也可能不是这样。如果它不代表一个有效地址,程序此时也会终止,但错误所报告的地址从本质上说是一个随机数。此时,问题的调试就极为困难。

If the random address is in an area in memory that contains data, the program usually aborts very quickly due to an illegal instruction or an illegal operand address (although data values sometimes represent valid instructions, they do not often make any sense).
如果程序此时还未失败,机器将开始执行根据非法下标所获得的虚假地址的指令,此时要调试出问题根源就更为困难了。如果这个随机地址位于一块存储数据的内存中,程序通常会很快终止,这通常是由于非法指令或非法的操作数地址所致 (尽管数据值有时也能代表有效的指令,但并不总是这样)。要想知道机器为什么会到达那个地方,唯一的线索是转移表调用函数时存储于堆栈中的返回地址。如果任何随机指令在执行时修改了堆栈或堆栈指针,那么连这个线索也消失了。

更糟的是,如果这个随机地址恰好位于一个函数的内部,那么该函数就会快乐地执行,修改谁也不知道的数据,直到它运行结束。但是,函数的返回地址并不是该函数所期望的保存于堆栈上的地址,而是另一个随机值。这个值就成为下一个指令的执行地址,计算机将在各个随机地址间跳转,执行位于那里的指令。

问题在于指令破坏了机器如何到达错误最后发生地点的线索。没有了这方面的信息,要查明问题的根源简直难如登天。如果你怀疑转移表有问题,可以在那个函数调用之前和之后各打印一条信息。如果被调用函数不再返回,用这种方法就可以看得很清楚。但困难在于人们很难认识到程序某个部分的失败可以是位于程序中相隔甚远的且不相关部分的一个转移表错误所引起的。

It is much easier to make sure that the subscript used in a jump table is within range in the first place.
一开始,保证转移表所使用的下标位于合法的范围是很容易做到的。

References

[1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/
[2] Pointers on C (C 和指针), https://www.cs.rit.edu/~kar/pointers.on.c/index.html
[3] 22.5 Function Pointers, https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Function-Pointers.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2272852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

这是什么操作?强制迁移?GitLab 停止中国区用户访问

大家好,我是鸭鸭! 全球知名代码托管平台 GitLab 发布通告,宣布不再为位于中国大陆、香港及澳门地区的用户提供访问服务,并且“贴心”建议,可以访问极狐 GitLab。 极狐 GitLab 是一家中外合资公司,宣称获得…

CDP集成Hudi实战-spark shell

[〇]关于本文 本文主要解释spark shell操作Hudi表的案例 软件版本Hudi1.0.0Hadoop Version3.1.1.7.3.1.0-197Hive Version3.1.3000.7.3.1.0-197Spark Version3.4.1.7.3.1.0-197CDP7.3.1 [一]使用Spark-shell 1-配置hudi Jar包 [rootcdp73-1 ~]# for i in $(seq 1 6); do s…

设计模式学习[15]---适配器模式

文章目录 前言1.引例2.适配器模式2.1 对象适配器2.2 类适配器 总结 前言 这个模式其实在日常生活中有点常见,比如我们的手机取消了 3.5 m m 3.5mm 3.5mm的接口,只留下了一个 T y p e − C Type-C Type−C的接口,但是我现在有一个 3.5 m m 3.…

数据挖掘——数据预处理

数据挖掘——数据预处理 数据预处理数据预处理 ——主要任务数据清洗如何处理丢失的数据如何处理噪声数据如何处理不一致数据 数据集成相关分析相关系数(也成为皮尔逊相关系数)协方差 数据规约降维法:PCA主成分分析降数据——抽样法数据压缩 数据预处理 数据预处理…

Unity-Mirror网络框架-从入门到精通之CCU示例

文章目录 前言什么是CCU?测试结果最后 前言 在现代游戏开发中,网络功能日益成为提升游戏体验的关键组成部分。Mirror是一个用于Unity的开源网络框架,专为多人游戏开发设计。它使得开发者能够轻松实现网络连接、数据同步和游戏状态管理。本文…

如何在 Ubuntu 22.04 上安装 Nagios 服务器教程

简介 在本教程中,我们将解释如何在 Ubuntu 22.04 上安装和配置 Nagios,使用 Apache 作为 Web 服务器,并通过 Let’s Encrypt Certbot 使用 SSL 证书进行保护。 Nagios 是一个强大的监控系统,它可以帮助组织在 IT 基础设施问题影…

大语言模型训练所需的最低显存,联邦大语言模型训练的传输优化技术

联邦大语言模型训练的传输优化技术 目录 联邦大语言模型训练的传输优化技术大语言模型训练所需的最低显存大语言模型训练所需的最低显存 基于模型微调、压缩和分布式并行处理的方法,介绍了相关开源模型及技术应用 核心创新点 多维度优化策略:综合运用基于模型微调、模型压缩和…

Javascript算法——贪心算法(一)

贪心算法详解(JavaScript)(局部最优->全局最优) 贪心算法(Greedy Algorithm)是一种在每一步选择中都采取当前状态下的最优选择(局部最优)的算法设计方法。通过局部最优解的累积&…

141.《mac m系列芯片安装mongodb详细教程》

文章目录 下载从官网下载安装包 下载后双击解压出文件夹安装文件名修改为 mongodb配置data存放位置和日志log的存放位置启动方式一方式二方式二:输入mongo报错以及解决办法 本人电脑 m2 pro,属于 arm 架构 下载 官网地址: mongodb官网 怎么查看自己电脑应该下载哪个版本,输入…

【ArcGISPro/GeoScenePro】裁剪和打包栅格数据

检查并处理扫描地图 数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 使用标准相机或扫描仪创建的数字影像通常存储在 RGB 颜色模型中,这意味着这些影像将由红色、绿色和蓝色三个栅格组成。 此扫描地图在提供给您之前已在坐标系…

【C++】B2109 统计数字字符个数

博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 💯前言💯题目描述与输入输出要求题目:统计数字字符个数样例输入输出 💯方法分析与代码实现方法 1:使用 fgets 读取输入并手动判断数字字符代码实…

基于32单片机的智能语音家居

一、主要功能介绍 以STM32F103C8T6单片机为控制核心,设计一款智能远程家电控制系统,该系统能实现如下功能: 1、可通过语音命令控制照明灯、空调、加热器、窗户及窗帘的开关; 2、可通过手机显示和控制照明灯、空调、窗户及窗帘的开…

git理解记录

文章目录 1. 背景2. 基本概念3. 日常工作流程4. 其他常见操作4.1 merge合并操作4.2 tag打标签操作4.3 remoute远程操作4.4 撤销修改 git理解记录 1. 背景 git作为分布式版本控制系统,开源且免费,相比svn集中式版本控制系统存在速度快(HEAD指针指向某次co…

开启宝塔Nginx防火墙支付返回失败解决方法

1.把/plugin.php加到Nginx防火墙URL白名单 包括支付服务器回调ip 2.如果支持带参数 /plugin.php?idzhifufm:notify 加到Nginx防火墙标准模式-URL白名单带参数 如果上面都不行 就把下面的IP加入白名单 39.107.193.172 47.94.194.103 47.104.216.190

平安产险安徽分公司携手安徽中医药临床研究中心附属医院 共筑儿童安全防护网

为响应金融知识普及教育号召,平安产险安徽分公司联动安徽中医药临床研究中心附属医院,于近日在朝霞小学举办了一场儿童安全防范与健康守护活动。此次活动旨在提升学生的安全防范意识,守护儿童健康成长,同时有力推动金融知识与传统…

Elasticsearch:Lucene 2024 年回顾

作者:来自 Elastic Chris Hegarty 2024 年对于 Apache Lucene 来说又是重要的一年。在本篇博文中,我们将探讨主要亮点。 Apache Lucene 在 2024 年表现出色,发布了许多版本,包括三年来的首次重大更新,其中包含令人兴奋…

Unity打包问题集(持续更新)

1.报错:CommandWithNoStdoutInvokationFailure: Unable to start ADB server. Please make sure the Android SDK is installed and is properly configured in the Editor. See the Console for more details. 大致意思就是打不开ADB的服务器,请检查安卓…

SpringBoot环境和Maven配置

SpringBoot环境和Maven配置 1. 环境准备2. Maven2.1 什么是Maven2.2 为什么要学 Maven2.3 创建一个 Maven项目2.4 Maven核心功能2.4.1 项目构建2.4.2 依赖管理2.4.3 Maven Help插件 2.5 Maven 仓库2.5.1本地仓库2.5.2 中央仓库2.5.3 私有服务器, 也称为私服 2.6 Maven设置国内源…

【HarmonyOS应用开发——ArkTS语言】欢迎界面(启动加载页)的实现【合集】

目录 😋环境配置:华为HarmonyOS开发者 📺演示效果: 📖实验步骤及方法: 一、在media文件夹中添加想要使用的图片素材​ 二、在entry/src/main/ets/page目录下创建Welcome.ets文件 1. 整体结构与组件声…

【数据可视化-11】全国大学数据可视化分析

🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…