news 2026/9/25 21:30:01

什么是回调函数?为什么要先把函数地址保存起来,再去调用,不能条件满足后,直接调用原来的函数吗

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
什么是回调函数?为什么要先把函数地址保存起来,再去调用,不能条件满足后,直接调用原来的函数吗

驱动 / BSP / FPGA + PS 代码,**回调函数(callback)**在这里用得非常多。
我用一句话定义 → 直观类比 → 代码级解释 → 你现在这类工程里的真实用途四层来讲。


一句话定义(先抓本质)

回调函数 = “我先把函数地址告诉你,将来在某个事件发生时,你再反过来调用我。”

重点不在“函数”,而在“把控制权交出去、等对方来调用”。


生活类比(非常重要)

普通函数(你主动调用)

你 → 打电话 → 餐厅

回调函数(你被动等通知)

你 → 留电话号码 → 餐厅 餐厅 → 菜好了 → 打电话给你

📌电话号码 = 函数指针
📌餐厅 = 框架 / 驱动 / 中断系统


C 语言里的本质(最核心)

1️⃣ 函数也是“地址”

voidmy_func(void){}

my_func本质是:

一个代码段的入口地址

2️⃣ 用“函数指针”保存这个地址

void(*callback)(void);callback=my_func;

现在:

callback 指向 my_func

3️⃣ 别人通过这个指针调用你

callback();// 实际执行 my_func()

📌你没主动调用,是“被调用”


标准回调示例(一步到位)

// 1. 定义回调类型typedefvoid(*event_cb_t)(int);// 2. 注册回调event_cb_tcb;voidregister_cb(event_cb_tf){cb=f;}// 3. 某个事件发生voidevent_happen(void){if(cb)cb(123);}// 4. 用户写的回调函数voidmy_callback(intv){printf("event value = %d\n",v);}intmain(){register_cb(my_callback);// 把“地址”交出去event_happen();// 框架反过来调你}

⚠️ 回调函数的本质特点(牢记)

特性说明
被动执行你不知道什么时候被调用
不能阻塞不能while(1)、不能长延时
不能假设上下文可能在中断 / 驱动 / 任务中
常配合状态机用于“事件触发”而不是“流程控制”

一、最直接的答案(先给结论)

因为“触发条件的代码”和“要执行的函数”往往不在同一个地方、甚至不在同一个模块、不同的人写的代码里。

所以:

  • 触发者不能、也不应该“写死”要调用哪个函数
  • 只能提供一个“接口”:
    👉谁想被调用,就把函数地址交出来

二、如果“直接调用”,会发生什么问题?

我们先假设你说的“直接调用”👇

❌ 写死调用(最简单但最糟)

if(dma_done){update_Carrier_DDS();}

看起来很简单,但有 4 个致命问题


❌ 问题 1:触发者“绑死”业务逻辑

DMA 驱动现在知道:

  • GNSS
  • Carrier
  • DDS

📌驱动层不该知道这些

驱动只应该知道:
“DMA 完成了”


❌ 问题 2:无法复用

今天你要:

  • DMA 完成 → 更新 DDS

明天你要:

  • DMA 完成 → 发消息
  • DMA 完成 → 切 buffer
  • DMA 完成 → 啥也不做

❓ 那怎么办?

👉改 DMA 驱动源码?

这是灾难级设计。


❌ 问题 3:多个用户怎么办?

DMA 完成后: - 模块 A 要知道 - 模块 B 也要知道

如果写死:

dma_done(){funcA();funcB();funcC();}

📌 这已经是“代码坟场”了。


❌ 问题 4:函数可能根本还不存在

在你现在的工程里:

  • 驱动 / 框架
  • 业务逻辑(GNSS / BB)

👉 往往是不同阶段、不同人、甚至不同仓库

驱动写的时候:

update_Carrier_DDS() 还不存在

三、为什么“保存函数地址”能解决这些问题?

我们来看同一件事,用回调怎么做。


✅ 回调设计

驱动 / 框架(只关心事件)
staticvoid(*dma_done_cb)(void)=0;voiddma_register_done_cb(void(*cb)(void)){dma_done_cb=cb;// 只存地址}voiddma_irq_handler(void){if(dma_done_cb)dma_done_cb();// 不关心是谁}
业务代码(决定“做什么”)
dma_register_done_cb(update_Carrier_DDS);

核心变化在哪里?

角色知道什么不知道什么
DMA 驱动DMA 完成DDS / GNSS / 业务逻辑
GNSS 模块要更新 DDSDMA 细节
联系方式函数地址源码依赖

👉彻底解耦


四、为什么“先保存”,不能“等条件到了再传”?

因为条件发生时,你已经没机会“再传”了。

典型场景 1:中断

DMA 完成 → 硬件拉中断 → CPU 立即跳中断入口

❓ 此时你去哪儿“再传函数”?

👉只能提前准备好


典型场景 2:硬件事件(PPS)

PPS 上升沿 → 硬件状态机 → 触发处理

📌事件发生是“被动”的


典型场景 3:异步事件

你不知道什么时候发生

👉只能提前登记


五、放到你现在这套 GNSS + FPGA 工程里

你这套系统是:

FPGA 产生事件 → AXI 状态变化 → PS 轮询 / 中断 → 更新 DDS / Code / Nav

如果不用回调,你就只能:

while(1){if(if_req)update_Carrier_DDS();if(code_req)update_Code_DDS();if(nav_req)update_Nav();}

📌 这就是你现在看到的“巨型 while(1)”

回调的意义是:

把 “if_req → update_Carrier_DDS”
从 while 里拆出去


六、一句话点破你这个问题的本质

“直接调用”假设了:触发者知道业务;
“回调”承认了:触发者不该知道业务。


七、再给你一个“工程级类比”(你一定懂)

AXI 寄存器设计

PL 不会写:

if (if_req) call_update_carrier_dds();

而是:

if_req <= 1;

PS 看到if_req后自己决定干什么。

📌回调 = 软件里的 AXI 状态位



版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 12:32:31

【Dify私有化部署终极指南】:从零开始掌握企业级部署全流程

第一章&#xff1a;Dify私有化部署概述Dify 是一个开源的低代码 AI 应用开发平台&#xff0c;支持快速构建基于大语言模型的智能应用。私有化部署允许企业将 Dify 完整运行在自有服务器或私有云环境中&#xff0c;保障数据安全与系统可控性&#xff0c;适用于对合规性、隐私保护…

作者头像 李华
网站建设 2026/9/24 6:27:33

聚势华商·智创未来 | 华商北京校友会年会庆典圆满落幕

2026年1月3日&#xff0c;星河华商书院华商北京校友会年会庆典在北京西国贸大酒店1号宴会厅隆重举行。本次活动以“聚势华商智创未来”为核心口号&#xff0c;汇聚“聚势凝心凝共识&#xff0c;智创赋能启新程&#xff0c;华商同心传薪火&#xff0c;逐梦同行向未来”的奋进力量…

作者头像 李华
网站建设 2026/9/25 16:30:03

GLM-4.6V-Flash-WEB模型推理速度实测报告

GLM-4.6V-Flash-WEB模型推理速度实测报告 在当前AI应用加速落地的背景下&#xff0c;多模态大模型正从实验室走向真实业务场景。但一个现实问题始终存在&#xff1a;很多视觉语言模型虽然能力强大&#xff0c;却“跑得太慢”——一次图文问答动辄耗时半秒以上&#xff0c;用户还…

作者头像 李华
网站建设 2026/9/25 18:11:49

GLM-4.6V-Flash-WEB在电子签名验证中的安全性考量

GLM-4.6V-Flash-WEB在电子签名验证中的安全性考量 在电子合同被广泛用于贷款审批、远程签约和政务办理的今天&#xff0c;一个看似合法的PDF文件可能暗藏玄机&#xff1a;签名区域被人用PS替换过&#xff0c;或者同一份合同里出现了三处一模一样的“手写签名”。这类视觉层面的…

作者头像 李华