深入理解 C 语言函数指针的高级用法:(void (*) (void *)) _IO_funlockfile
深入理解 C 语言函数指针的高级用法
函数指针是 C 语言中极具威力的特性,广泛用于实现回调、动态函数调用以及灵活的程序设计。然而,复杂的函数指针声明常常让即使是有经验的开发者也感到困惑。本文将从函数指针的基本概念出发,逐步解析复杂的函数指针声明,结合实际应用场景,帮助高级 C 程序员深入理解和掌握这一工具。
1. 基本概念:什么是函数指针?
函数指针 是指向函数的指针,它允许程序在运行时调用不同的函数。
基本形式:
返回类型 (*指针名)(参数列表);
例子:
int add(int a, int b) {return a + b;
}int (*func_ptr)(int, int) = add; // 定义一个指向 add 的函数指针
printf("%d\n", func_ptr(3, 5)); // 输出 8
2. 深入分析 (void (*) (void *))
语法
让我们逐层解析以下语法:
(void (*) (void *))
void *
:参数是一个通用指针,可以传递任何类型的指针。void (*)
:表示一个返回值为void
的函数指针。- 完整含义:这是一个指向以
void *
为参数,返回值为void
的函数的指针。
例子:
void my_callback(void *arg) {printf("Callback invoked with arg: %p\n", arg);
}void (*callback)(void *) = my_callback; // 定义函数指针
callback((void *)0x1234); // 调用回调函数,输出: Callback invoked with arg: 0x1234
3. 函数指针的高级用法
3.1 回调机制
回调函数是函数指针最常见的应用场景之一。在系统设计中,通过函数指针可以让调用者决定函数的具体实现。
例子:注册回调
#include <stdio.h>typedef void (*Callback)(int); // 定义函数指针类型void event_handler(int event_id) {printf("Event %d handled!\n", event_id);
}void register_callback(Callback cb) {cb(42); // 调用回调
}int main() {register_callback(event_handler); // 输出: Event 42 handled!return 0;
}
3.2 动态函数调用
动态加载库时,可以通过 dlsym
动态解析符号,使用函数指针调用目标函数。
例子:动态加载库函数
#include <dlfcn.h>
#include <stdio.h>int main() {void *handle = dlopen("libm.so.6", RTLD_LAZY); // 加载数学库if (!handle) {perror("dlopen failed");return 1;}double (*cos_func)(double) = dlsym(handle, "cos"); // 获取 cos 函数地址printf("cos(0) = %f\n", cos_func(0)); // 输出: cos(0) = 1.000000dlclose(handle); // 关闭库return 0;
}
3.3 多级指针与函数指针数组
函数指针可以存储在数组中,甚至通过多级指针访问。
例子:函数指针数组
#include <stdio.h>int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }int main() {int (*operations[2])(int, int) = {add, subtract}; // 函数指针数组printf("Add: %d\n", operations[0](10, 5)); // 输出: Add: 15printf("Subtract: %d\n", operations[1](10, 5)); // 输出: Subtract: 5return 0;
}
3.4 嵌套函数指针
函数指针本身可以作为参数或返回值,用于更复杂的逻辑设计。
例子:返回函数指针
#include <stdio.h>int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }// 这里的解释见下文
int (*get_operation(char op))(int, int) {if (op == '+') return add;if (op == '-') return subtract;return NULL;
}int main() {char op = '+';int (*operation)(int, int) = get_operation(op); // 获取函数指针printf("Result: %d\n", operation(10, 5)); // 输出: Result: 15return 0;
}
4. 实际案例:_IO_cleanup_region_start
回到具体的例子:
_IO_cleanup_region_start((void (*) (void *)) &_IO_funlockfile, s);
-
语法分解:
(void (*) (void *))
:这是类型强制转换,表示将_IO_funlockfile
转换为void (*)(void *)
类型的函数指针。_IO_funlockfile
:用于解锁流的函数,其原型可能为:void _IO_funlockfile(void *);
-
作用:
- 将
_IO_funlockfile
注册为清理函数,并将s
(流指针)作为参数。当_IO_cleanup_region_end
被调用时,清理函数会被触发,执行流解锁操作。
- 将
5. 总结与注意事项
-
掌握基本规则:
- 自内向外解析复杂的函数指针声明。
- 使用
typedef
简化复杂的声明。
-
注意线程安全:
- 多线程环境下,回调函数和动态函数调用需确保数据安全性。
-
实际应用场景:
- 回调机制:如事件驱动、信号处理。
- 动态函数调用:如
dlsym
。 - 嵌套与多级指针:实现灵活的逻辑控制。
-
代码示例总结:
通过本文的讲解和示例,高级开发者可以更深入地理解函数指针的高级用法,并在实际项目中灵活应用这一强大工具。
理解 int (*get_operation(char op))(int, int)
的语法
1. 分解声明
int (*get_operation(char op))(int, int)
是一个复杂的函数声明,逐步解析如下:
-
get_operation(char op)
表示这是一个函数,函数名为get_operation
,接收一个参数op
,类型为char
。 -
(*get_operation(char op))
表明get_operation
返回的是一个指针,而不是一个普通的值。 -
(*get_operation(char op))(int, int)
指针所指向的内容是一个函数,这个函数的参数为两个int
类型,并返回一个int
。 -
最终含义
get_operation
是一个返回值为函数指针的函数,这个函数指针指向的函数,接受两个int
参数,返回一个int
。
2. 结合代码理解
我们来看代码:
int (*get_operation(char op))(int, int) {if (op == '+') return add; // 返回指向 add 函数的指针if (op == '-') return subtract; // 返回指向 subtract 函数的指针return NULL; // 无匹配时返回空指针
}
-
add
和subtract
这两个函数的签名为int add(int a, int b)
和int subtract(int a, int b)
,返回一个int
,且参数为两个int
。 -
return add
add
本身是一个函数名,在此处被隐式转换为函数指针,作为get_operation
的返回值。
3. 等价的 typedef
简化
函数指针的声明较复杂,可以用 typedef
简化:
#include <stdio.h>typedef int (*operation_t)(int, int); // 定义一个函数指针类型int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }operation_t get_operation(char op) { // 返回类型为函数指针类型if (op == '+') return add;if (op == '-') return subtract;return NULL;
}int main() {char op = '+';operation_t operation = get_operation(op); // 获取函数指针printf("Result: %d\n", operation(10, 5)); // 输出: Result: 15return 0;
}
4. 其他类似例子
返回函数指针的函数
#include <stdio.h>int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b == 0 ? 0 : a / b; }int (*choose_function(char op))(int, int) {if (op == '*') return multiply;if (op == '/') return divide;return NULL;
}int main() {char op = '*';int (*func)(int, int) = choose_function(op); // 获取函数指针if (func != NULL) {printf("Result: %d\n", func(10, 5)); // 输出: Result: 50} else {printf("No valid function found.\n");}return 0;
}
解释:
-
choose_function(char op)
函数名为choose_function
,接收一个char
类型参数op
。 -
int (*choose_function(char op))(int, int)
表示choose_function
的返回值是一个指向函数的指针,这个函数接受两个int
参数并返回一个int
。
函数指针嵌套使用
函数指针还可以作为其他函数的参数或返回值。
例子:函数指针作为参数
#include <stdio.h>int calculate(int a, int b, int (*operation)(int, int)) {return operation(a, b);
}int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }int main() {printf("Add: %d\n", calculate(10, 5, add)); // 输出: Add: 15printf("Subtract: %d\n", calculate(10, 5, subtract)); // 输出: Subtract: 5return 0;
}
5. 总结
-
复杂函数指针声明的解析方法:
- 从内向外逐层解析。
- 关注
()
和*
的位置区分函数和指针。
-
用
typedef
简化复杂声明:- 使用
typedef
定义函数指针类型,使代码更易读。
- 使用
-
实际场景:
- 返回函数指针常用于动态函数选择、策略模式。
- 函数指针的灵活性使其在回调、动态库加载等场景非常有用。
通过例子可以看出,理解复杂的函数指针声明关键在于分解其语法,结合实际应用场景能更好地掌握这一强大的工具。
Advanced Use of Function Pointers in C
Function pointers are one of the most powerful features of C, allowing for dynamic function calls, callbacks, and flexible program design. However, complex function pointer declarations can be challenging even for experienced developers. This blog will explore advanced concepts and practical applications of function pointers, with a focus on parsing and utilizing challenging syntax like (void (*) (void *))
.
1. What Are Function Pointers?
A function pointer is a pointer that points to a function’s memory address, enabling dynamic function calls during runtime.
Basic Syntax:
return_type (*pointer_name)(parameter_list);
Example:
int add(int a, int b) {return a + b;
}int (*func_ptr)(int, int) = add; // Define a function pointer to `add`
printf("%d\n", func_ptr(3, 5)); // Output: 8
2. Understanding (void (*) (void *))
Let’s break down the syntax (void (*) (void *))
step by step:
void *
: A generic pointer that can point to any type.void (*)
: A pointer to a function returningvoid
.void (*) (void *)
: A pointer to a function that takes avoid *
as its argument and returnsvoid
.
This syntax is used to define or cast a function pointer.
Example:
void my_callback(void *arg) {printf("Callback invoked with arg: %p\n", arg);
}void (*callback)(void *) = my_callback; // Define a function pointer
callback((void *)0x1234); // Invoke the function pointer, Output: Callback invoked with arg: 0x1234
3. Advanced Use Cases for Function Pointers
3.1 Callback Mechanisms
Callback functions are a common use case for function pointers, allowing a caller to pass a function as an argument to be executed later.
Example: Registering a Callback
#include <stdio.h>typedef void (*Callback)(int); // Define a function pointer typevoid event_handler(int event_id) {printf("Event %d handled!\n", event_id);
}void register_callback(Callback cb) {cb(42); // Call the callback
}int main() {register_callback(event_handler); // Output: Event 42 handled!return 0;
}
3.2 Dynamic Function Calls
Function pointers are essential in scenarios like dynamically loading libraries and calling functions at runtime.
Example: Using dlsym
to Call a Function Dynamically
#include <dlfcn.h>
#include <stdio.h>int main() {void *handle = dlopen("libm.so.6", RTLD_LAZY); // Load the math libraryif (!handle) {perror("dlopen failed");return 1;}double (*cos_func)(double) = dlsym(handle, "cos"); // Resolve the `cos` functionprintf("cos(0) = %f\n", cos_func(0)); // Output: cos(0) = 1.000000dlclose(handle); // Close the libraryreturn 0;
}
3.3 Function Pointer Arrays
Function pointers can be stored in arrays to implement flexible dispatch tables or emulate polymorphism.
Example: Function Pointer Arrays
#include <stdio.h>int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }int main() {int (*operations[2])(int, int) = {add, subtract}; // Array of function pointersprintf("Add: %d\n", operations[0](10, 5)); // Output: Add: 15printf("Subtract: %d\n", operations[1](10, 5)); // Output: Subtract: 5return 0;
}
3.4 Nested Function Pointers
Function pointers can be used as return types, enabling the implementation of factory-like patterns.
Example: Returning a Function Pointer
#include <stdio.h>int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }int (*get_operation(char op))(int, int) {if (op == '+') return add;if (op == '-') return subtract;return NULL;
}int main() {char op = '+';int (*operation)(int, int) = get_operation(op); // Get a function pointerprintf("Result: %d\n", operation(10, 5)); // Output: Result: 15return 0;
}
4. Real-World Case: _IO_cleanup_region_start
In the context of the GNU C Library, the following code registers a cleanup function for a stream:
_IO_cleanup_region_start((void (*) (void *)) &_IO_funlockfile, s);
Explanation:
-
Type Casting:
_IO_funlockfile
is a function, likely declared as:void _IO_funlockfile(void *);
(void (*) (void *))
casts_IO_funlockfile
to match the expected function pointer type.
-
Usage:
_IO_cleanup_region_start
registers_IO_funlockfile
as a cleanup function for the streams
. This ensures that_IO_funlockfile(s)
will be called even if the function exits early or encounters an error.
5. Tips for Working with Complex Function Pointers
-
Break Down the Declaration:
- Use tools like cdecl to decode complex function pointer syntax.
- Parse the declaration from the inside out.
-
Use
typedef
for Clarity:- Simplify complex declarations by using
typedef
.
Example:
typedef void (*Callback)(void *); Callback cb = my_callback;
- Simplify complex declarations by using
-
Understand Function Pointer Arrays:
- Arrays of function pointers can emulate polymorphism and simplify dispatch tables.
-
Dynamic Typing:
- Combine function pointers with
void *
for maximum flexibility.
- Combine function pointers with
6. Conclusion
Function pointers are a cornerstone of advanced C programming, enabling dynamic function calls, callbacks, and flexible system design. Understanding and mastering their syntax, particularly in complex cases like (void (*) (void *))
, empowers developers to write efficient and modular code. By breaking down declarations and exploring real-world examples, you can confidently integrate function pointers into your projects.
后记
2025年1月27日于山东日照。
相关文章:

深入理解 C 语言函数指针的高级用法:(void (*) (void *)) _IO_funlockfile
深入理解 C 语言函数指针的高级用法 函数指针是 C 语言中极具威力的特性,广泛用于实现回调、动态函数调用以及灵活的程序设计。然而,复杂的函数指针声明常常让即使是有经验的开发者也感到困惑。本文将从函数指针的基本概念出发,逐步解析复杂…...

【JavaSE】图书管理系统
前言:为了巩固之前学习的java知识点,我们用之前学习的java知识点(方法,数组,类和对象,封装,继承,多态,抽象类,接口)来实现一个简单的图书管理系统…...

【C++数论】880. 索引处的解码字符串|2010
本文涉及知识点 数论:质数、最大公约数、菲蜀定理 LeetCode880. 索引处的解码字符串 给定一个编码字符串 s 。请你找出 解码字符串 并将其写入磁带。解码时,从编码字符串中 每次读取一个字符 ,并采取以下步骤: 如果所读的字符是…...

C++/stack_queue
目录 1.stack 1.1stack的介绍 1.2stack的使用 练习题: 1.3stack的模拟实现 2.queue的介绍和使用 2.1queue的介绍 2.2queue的使用 2.3queue的模拟实现 3.priority_queue的介绍和使用 3.1priority_queue的介绍 3.2priority_queue的使用 欢迎 1.stack 1.1stack…...

浅谈APP之历史股票通过echarts绘图
浅谈APP之历史股票通过echarts绘图 需求描述 今天我们需要做一个简单的历史股票收盘价格通过echarts进行绘图,效果如下: 业务实现 代码框架 代码框架如下: . 依赖包下载 我们通过网站下载自己需要的涉及的图标,勾选之后进…...

Ubuntu 20.04 x64下 编译安装ffmpeg
试验的ffmpeg版本 4.1.3 本文使用的config命令 ./configure --prefixhost --enable-shared --disable-static --disable-doc --enable-postproc --enable-gpl --enable-swscale --enable-nonfree --enable-libfdk-aac --enable-decoderh264 --enable-libx265 --enable-libx…...

【橘子Kibana】Kibana的分析能力Analytics简易分析
一、kibana是啥,能干嘛 我们经常会用es来实现一些关于检索,关于分析的业务。但是es本身并没有UI,我们只能通过调用api来完成一些能力。而kibana就是他的一个外置UI,你完全可以这么理解。 当我们进入kibana的主页的时候你可以看到这样的布局。…...

【STM32】-TTP223B触摸开关
前言 本文章旨在记录博主STM32的学习经验,我自身也在不断的学习当中,如果文章有写的不对的地方,欢迎各位大佬批评指正。 准备工作 今天这篇文章介绍的是触摸开关这一外围硬件。 ST-link调试器STM32最小系统板单路TTP223B触摸传感器模块LE…...

三星手机人脸识别解锁需要点击一下电源键,能够不用点击直接解锁吗
三星手机的人脸识别解锁功能默认需要滑动或点击屏幕来解锁。这是为了增强安全性,防止误解锁的情况。如果希望在检测到人脸后直接进入主界面,可以通过以下设置调整: 打开设置: 进入三星手机的【设置】应用。 进入生物识别和安全&a…...

Frida使用指南(三)- Frida-Native-Hook
1.Process、Module、Memory基础 1.Process Process 对象代表当前被Hook的进程,能获取进程的信息,枚举模块,枚举范围等 2.Module Module 对象代表一个加载到进程的模块(例如,在 Windows 上的 DLL,或在 Linux/Android 上的 .so 文件), 能查询模块的信息,如模块的基址、名…...

网络安全 | F5-Attack Signatures-Set详解
关注:CodingTechWork 创建和分配攻击签名集 可以通过两种方式创建攻击签名集:使用过滤器或手动选择要包含的签名。 基于过滤器的签名集仅基于在签名过滤器中定义的标准。基于过滤器的签名集的优点在于,可以专注于定义用户感兴趣的攻击签名…...

004 mybatis基础应用之全局配置文件
文章目录 配置内容properties标签typeAlias标签mappers标签 配置内容 SqlMapConfig.xml中配置的内容和顺序如下: properties(属性) settings(全局配置参数) typeAliases(类型别名) typeHandler…...

【岛屿个数——BFS / DFS,“外海”】
题目 推荐阅读 AcWing 4959. 岛屿个数(两种解法,通俗解释) - AcWing 1.岛屿个数 - 蓝桥云课 (lanqiao.cn) 代码 #include <bits/stdc.h> using namespace std; #define x first #define y second int dx4[4] {-1, 0, 1, 0}, dy4[4] …...

MySQL常用数据类型和表的操作
文章目录 (一)常用数据类型1.数值类2.字符串类型3.二进制类型4.日期类型 (二)表的操作1查看指定库中所有表2.创建表3.查看表结构和查看表的创建语句4.修改表5.删除表 (三)总代码 (一)常用数据类型 1.数值类 BIT([M]) 大小:bit M表示每个数的位数,取值范围为1~64,若…...

2025_1_27 C语言内存,递归,汉诺塔问题
1.c程序在内存中的布局 代码段(Code Segment) 位置:通常位于内存的最低地址。 用途:存储程序的可执行指令。 特点:只读,防止程序运行时被修改。数据段(Data Segment) 位置…...

开源音乐管理软件Melody
本文软件由网友 heqiusheng 推荐。不过好像已经是一年前了 😂 简介 什么是 Melody ? Melody 是你的音乐精灵,旨在帮助你更好地管理音乐。目前的主要能力是帮助你将喜欢的歌曲或者音频上传到音乐平台的云盘。 主要功能包括: 歌曲…...

Nginx开发01:基础配置
一、下载和启动 1.下载、使用命令行启动:Web开发:web服务器-Nginx的基础介绍(含AI文稿)_nginx作为web服务器,可以承担哪些基本任务-CSDN博客 注意:我配置的端口是81 2.测试连接是否正常 访问Welcome to nginx! 如果…...

【TCP 协议】确认应答机制 超时重传 三次握手 四次挥手
TCP报文首部 确认应答机制 TCP 是可靠的,指的是它能够确保数据从源端准确无误地传输到目的端。 当客户端和服务器通信时,客户端向服务器发送报文,那么,客户端怎么知道服务器已经收到报文了呢? 服务器收到客户端发的报…...

jenkins-k8s pod方式动态生成slave节点
一. 简述: 使用 Jenkins 和 Kubernetes (k8s) 动态生成 Slave 节点是一种高效且灵活的方式来管理 CI/CD 流水线。通过这种方式,Jenkins 可以根据需要在 Kubernetes 集群中创建和销毁 Pod 来执行任务,从而充分利用集群资源并实现更好的隔离性…...

基于vue和elementui的简易课表
本文参考基于vue和elementui的课程表_vue实现类似课程表的周会议列表-CSDN博客,原程序在vue3.5.13版本下不能运行,修改两处: 1)slot-cope改为v-slot 2)return background-color:rgb(24 144 255 / 80%);color: #fff; …...

可用的IPv6公共DNS(2025年1月更新)
境内IPv6 DNS: 1. 腾讯DNS:2402:4e00:: 2. 阿里DNS:2400:3200::1、2400:3200:baba::1 3. ISP(电信服务运营商)的IPv6 DNS,请以各ISP实际下发的为准,或拨打10000、10010、10086等号码询问人工…...

c高级复习
c高级复习...

电子信息工程专业主要研究哪一方面东西?
序言: 如今科技发展那叫一个迅猛,电子信息专业可是站在这股浪潮的 C 位,狠狠推动着社会向前跑。这专业就像一座神奇桥梁,把虚拟数字和现实生活紧紧相连,把那些信号变成咱们看到的画面、听到的声音。你看,从…...

RU 19.26安装(手工安装各个补丁)
使用手工方式打RU19.26 参考文档: Supplemental Readme - Grid Infrastructure Release Update 12.2.0.1.x / 18c /19c (Doc ID 2246888.1) 操作步骤: 1 Stop the CRS managed resources running from DB homes. 2 Run the pre root script. 3 Patch GI…...

深入理解Pytest中的Setup和Teardown
关注开源优测不迷路 大数据测试过程、策略及挑战 测试框架原理,构建成功的基石 在自动化测试工作之前,你应该知道的10条建议 在自动化测试中,重要的不是工具 对于简单程序而言,使用 Pytest 运行测试直截了当。然而,当你…...

如何利用AI工具来进行数据分析
利用AI工具进行数据分析可以显著提高效率和准确性,以下是详细步骤和方法: 1. 明确分析目标 在开始数据分析之前,首先需要明确分析的目标和问题。这包括确定需要解决的问题、期望的见解或结果,以及选择合适的AI工具和方法。 2. …...

具身智能体俯视全局的导航策略!TopV-Nav: 解锁多模态语言模型在零样本目标导航中的顶视空间推理潜力
作者:Linqing Zhong, Chen Gao, Zihan Ding, Yue Liao, Si Liu 单位:北京航空航天大学,新加坡国立大学,香港中文大学多模态实验室 论文标题:TopV-Nav: Unlocking the Top-View Spatial Reasoning Potential of MLLM …...

npm:升级自身时报错:EBADENGINE
具体报错信息如下: 1.原因分析 npm和当前的node版本不兼容。 // 当前实际版本: Actual: {"npm":"10.2.4","node":"v20.11.0"}可以通过官网文档查看与自己 node 版本 兼容的是哪一版本的npm,相对应进行更新即可…...

微信小程序实现自定义日历功能
文章目录 1. 创建日历组件实现步骤:2. 代码实现过程3. 实现效果图4. 关于作者其它项目视频教程介绍 1. 创建日历组件实现步骤: 创建日历组件:首先,你需要创建一个日历组件,包含显示日期的逻辑。样式设计:为…...

Vue 3 中的 toRef 与 toRefs:使用与案例解析
在 Vue 3 的响应式系统中,toRef 和 toRefs 是两个非常实用的工具函数。它们主要用于将响应式对象的属性转换为单独的 ref,以便在模板或逻辑中更方便地使用。本文将详细介绍 toRef 和 toRefs 的用法,并通过一个老师信息的案例来演示它们的实际…...