当前位置: 首页 > news >正文

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

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

相关文章:

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

C 语言函数指针 {Pointers to Functions, Function Pointers} 1. Pointers to Functions (函数指针)2. Function Pointers (函数指针)2.1. Declaring Function Pointers2.2. Assigning Function Pointers2.3. Calling Function Pointers 3. Jump Tables (转移表)References 1. …...

66.基于SpringBoot + Vue实现的前后端分离-律师事务所案件管理系统(项目 + 论文)

项目介绍 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装律师事务所案件管理系统软件来发挥其高效地信息处理的作用…...

Docker容器中Elasticsearch内存不足问题排查与解决方案

在使用Docker运行Elasticsearch(ES)时,可能会遇到内存不足的问题,导致ES无法启动。以下是一次完整的排查和解决过程。 问题描述 在启动ES时,日志提示如下错误: # Native memory allocation (mmap) failed…...

Ubuntu 下测试 NVME SSD 的读写速度

在 Ubuntu 系统下,测试 NVME SSD 的读写速度,有好多种方法,常用的有如下几种: 1. Gnome-disks Gnome-disks(也称为“Disks”)是 GNOME 桌面环境中的磁盘管理工具,有图形界面,是测试…...

Neo4j的部署和操作

注:本博文展示部署哥操作步骤和命令,具体报告及运行截图可通过上方免费资源绑定下载 一.数据库的部署与配置 在单个节点上对进行数据库的单机部署 (1)上传neo4j-community-3.5.30-unix.tar.gz到hadoop1的/export/so…...

react axios 优化示例

使用 axios 是 React 项目中非常常见的 HTTP 请求库。为了提升 axios 在 React 中的性能、可维护性和用户体验,我们可以从 代码组织、请求优化 和 用户体验优化 多个角度进行详细的优化。 一、安装与基础配置 安装 axios npm install axios创建 Axios 实例 为了更好地管理…...

探索数字化展馆:开启科技与文化的奇幻之旅

在科技飞速发展的当下,数字展馆作为一种新兴的展示形式,正逐渐走进大众的视野。数字展馆不仅仅是传统展馆的简单“数字化升级”,更是融合了多媒体、数字化技术以及人机交互等前沿科技的创新产物。 数字展馆借助VR、AR、全息投影等高科技手段&…...

基于深度学习的视觉检测小项目(七) 开始组态界面

开始设计和组态画面。 • 关于背景和配色 在组态画面之前,先要确定好画面的风格和色系。如果有前端经验和美术功底,可以建立自己的配色体系。像我这种工科男,就只能从网络上下载一些别人做好的优秀界面,然后在photo shop中抠取色…...

AI赋能跨境电商:魔珐科技3D数字人破解出海痛点

跨境出海进入狂飙时代,AI应用正在深度渗透并重塑着跨境电商产业链的每一个环节,迎来了发展的高光时刻。生成式AI时代的大幕拉开,AI工具快速迭代,为跨境电商行业的突破与飞跃带来了无限可能性。 由于跨境电商业务自身特性鲜明&…...

【C/C++】nlohmann::json从文件读取json,并进行解析打印,实例DEMO

使用 json::parse 函数将JSON格式的字符串解析为 nlohmann::json 对象。这个函数支持多种输入源&#xff0c;包括字符串、文件流等。 #include <iostream> #include <nlohmann/json.hpp> #include <fstream>using json nlohmann::json;int main() {// 解析…...

安装Anaconda搭建Python环境,并使用VSCode作为IDE运行Python脚本

下面详细说明如何安装Anaconda搭建Python环境&#xff0c;并使用VSCode作为编辑器运行Python脚本的过程&#xff1a; 1. 下载Anaconda 访问Anaconda的官方网站&#xff1a;https://www.anaconda.com/products/distribution 3. 根据您的操作系统选择适合的版本下载。Anaconda支…...

我用AI学Android Jetpack Compose之入门篇(1)

这篇我们先来跑通第一个Android Jetpack Compose工程&#xff0c;现在新版本的Android Studio&#xff0c;新建工程选择Empty Activity默认就会开启Jetpack Compose的支持&#xff0c;再次声明&#xff0c;答案来自 通义千问Ai 文章目录 1.用Android Jetpack Compose需要安装什…...

使用 Docker 查看 Elasticsearch 错误日志

在使用 Elasticsearch&#xff08;简称 ES&#xff09;的过程中&#xff0c;我们可能会遇到各种问题。为了快速定位和解决这些问题&#xff0c;查看错误日志是关键。本文将介绍如何使用 Docker 查看 Elasticsearch 的错误日志&#xff0c;并提供一些实用技巧。 1. 安装 Docker…...

使用Apache Mahout制作 推荐引擎

目录 创建工程 基本概念 关键概念 基于用户与基于项目的分析 计算相似度的方法 协同过滤 基于内容的过滤 混合方法 创建一个推荐引擎 图书评分数据集 加载数据 从文件加载数据 从数据库加载数据 内存数据库 协同过滤 基于用户的过滤 基于项目的过滤 添加自定…...

Elasticsearch:利用 AutoOps 检测长时间运行的搜索查询

作者&#xff1a;来自 Elastic Valentin Crettaz 了解 AutoOps 如何帮助你调查困扰集群的长期搜索查询以提高搜索性能。 AutoOps 于 11 月初在 Elastic Cloud Hosted 上发布&#xff0c;它通过性能建议、资源利用率和成本洞察、实时问题检测和解决路径显著简化了集群管理。 Au…...

python二元表达式 三元表达式

目录 二元表达式必须要有else,示例: 二元表达式: 三元表达式 可以嵌套成多元表达式 python 代码中,有时写 if else比较占行,把代码变一行的方法就是二元表达式, 二元表达式必须要有else,示例: if img is None:breakcv2.imwrite("aaa.jpg", img) if coun…...

计算机网络 (22)网际协议IP

一、IP协议的基本定义 IP协议是Internet Protocol的缩写&#xff0c;即因特网协议。它是TCP/IP协议簇中最核心的协议&#xff0c;负责在网络中传送数据包&#xff0c;并提供寻址和路由功能。IP协议为每个连接在因特网上的主机&#xff08;或路由器&#xff09;分配一个唯一的IP…...

【UI自动化测试】selenium八种定位方式

&#x1f3e1;个人主页&#xff1a;謬熙&#xff0c;欢迎各位大佬到访❤️❤️❤️~ &#x1f472;个人简介&#xff1a;本人编程小白&#xff0c;正在学习互联网求职知识…… 如果您觉得本文对您有帮助的话&#xff0c;记得点赞&#x1f44d;、收藏⭐️、评论&#x1f4ac;&am…...

REMARK-LLM:用于生成大型语言模型的稳健且高效的水印框架

REMARK-LLM:用于生成大型语言模型的稳健且高效的水印框架 前言 提出这一模型的初衷为了应对大量计算资源和数据集出现伴随的知识产权问题。使用LLM合成类似人类的内容容易受到恶意利用,包括垃圾邮件和抄袭。 ChatGPT等大语言模型LLM的开发取得的进展标志着人机对话交互的范式…...

Android SPRD 工模测试修改

设备有两颗led灯&#xff0c;工模测试需全亮 vendor/sprd/proprietories-source/factorytest/testitem/led.cpp -13,6 13,10 typedef enum{#define LED_BLUE "/sys/class/leds/blue/brightness"#define LED_RED …...

MATLAB遍历生成20到1000个节点的无线通信网络拓扑推理数据

功能&#xff1a; 遍历生成20到1000个节点的无线通信网络拓扑推理数据&#xff0c;包括网络拓扑和每个节点发射的电磁信号&#xff0c;采样率1MHz/3000&#xff0c;信号时长5.7s&#xff0c;单帧数据波形为实采 数据生成效果&#xff1a; 拓扑及空间位置&#xff1a; 节点电磁…...

小番茄C盘清理:专业高效的电脑磁盘清理工具

在使用电脑的过程中&#xff0c;我们常常会遇到系统盘空间不足、磁盘碎片过多、垃圾文件堆积等问题&#xff0c;这些问题不仅会导致电脑运行缓慢&#xff0c;还可能引发系统崩溃。为了解决这些问题&#xff0c;小番茄C盘清理应运而生。它是一款专业的C盘清理软件&#xff0c;能…...

技巧小结:根据寄存器手册写常用外设的驱动程序

需求&#xff1a;根据STM32F103寄存器手册写DMA模块的驱动程序 一、分析标准库函数的写法&#xff1a; 各个外设的寄存器地址定义在stm32f10x.h文件中&#xff1a;此文件由芯片厂家提供;内核的有关定义则定义在core_cm3.h文件中&#xff1a;ARM提供; 1、查看外设区域多级划分…...

AI大神吴恩达-提示词课程笔记

如何有效编写提示词 在学习如何与语言模型&#xff08;如ChatGPT&#xff09;交互时&#xff0c;编写清晰且高效的提示词&#xff08;Prompt&#xff09;是至关重要的。本课程由ESA提供&#xff0c;重点介绍了提示词工程&#xff08;Prompt Engineering&#xff09;的两个核心…...

python版若依框架开发:后端开发规范

python版若依框架开发 从0起步,扬帆起航。 python版若依部署代码生成指南,迅速落地CURD!项目结构解析前端开发规范后端开发规范文章目录 python版若依框架开发1.启动命令2.配置⽂件3.上传配置1.启动命令 本项⽬⾃定义了两个启动命令 pyhton app.py --env=devpython app.p…...

34、协程

在Linux系统中&#xff0c;协程是一种轻量级的线程&#xff0c;它们允许在多个任务之间切换&#xff0c;而不需要操作系统的线程调度。协程可以分为有栈协程和无栈协程&#xff0c;以及对称协程和非对称协程。 有栈协程 有栈协程每个协程都有自己的栈空间&#xff0c;允许协程…...

网络协议通俗易懂详解指南

目录 1. 什么是网络协议? 1.1 协议的本质 1.2 为什么需要协议? 1.3 协议分层的概念 2. TCP协议详解 - 可靠的信使 📦 2.1 TCP是什么? 2.2 TCP的核心特性 🔗 面向连接 🛡️ 可靠传输 📊 流量控制 2.3 TCP三次握手 - 建立连接 2.4 TCP四次挥手 - 断开连接…...

【工作记录】接口功能测试总结

如何对1个接口进行接口测试 一、单接口功能测试 1、接口文档信息 理解接口文档的内容&#xff1a; 请求URL: https://[ip]:[port]/xxxserviceValidation 请求方法: POST 请求参数: serviceCode(必填), servicePsw(必填) 响应参数: status, token 2、编写测试用例 2.1 正…...

unix/linux,sudo,其内部结构机制

我们现在深入sudo的“引擎室”,探究其内部的结构和运作机制。这就像我们从观察行星运动,到深入研究万有引力定律的数学表达和物理内涵一样,是理解事物本质的关键一步。 sudo 的内部结构与机制详解 sudo 的执行流程可以看作是一系列精心设计的步骤,确保了授权的准确性和安…...

Ubuntu18.6 学习QT问题记录以及虚拟机安装Ubuntu后的设置

Ubuntu安装 1、VM 安装 Ubuntu后窗口界面太小 Vmware Tools 工具安装的有问题 处理办法&#xff1a; 1、重新挂载E:\VMwareWorkstation\linux.iso文件&#xff0c;该文件在VMware安装目录下 2、Ubuntu桌面出现vmtools共享文件夹&#xff0c;将gz文件拷贝至本地&#xff0c;解…...