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

函数指针的回调函数与函数跳转执行

在C语言中,函数指针是一项重要的知识点,到了嵌入式,这个知识点的重要性更是如此,例如回调函数Bootloader的绝对地址跳转,让我们不得不重视这个知识点。本章就是讲解函数指针的两种主要用法。

1.函数指针定义

认识一个人时,外貌通常是第一印象的来源。函数指针同样具备独特的语法表现形式,其声明方式直接反映了它的本质特性。


void cal_sum(void);1.普通函数定义
/*(*func_ptr)括号不能丢,丢了就是返回一个指针的普通函数*/
void (*func_ptr)(void);/*指针函数指向一个函数的地址*/
func_ptr= &cal_sum;2.typedef 函数指针typedef void (*func_ptr)(void);/*声明了一个函数指针变量 pFun*/
func_ptr pFun=cal_sum;

第一个方法看着好像跟我们的指针变量其实也差不多,其实指针的本质都是如此,即存放的都是一个地址罢了。

第二个的typedef关键字的使用方法好像跟之前使用的不太一样,其实这个的意思是要定义的类型是void (*)(void),没有输入参数,返回值为void的函数指针,定义的别名是func_ptr

他们实现的效果都是一样的。并无区别。

2.回调函数:

2.1 回调函数的概念

回调函数本质上是通过函数指针来实现的。简单来说,当我们把一个函数的指针(即函数的入口地址)作为参数传递给另一个函数时,在被调用的函数内部,就可以通过这个指针来调用其所指向的函数,这个被调用的函数就被称为回调函数。

回调函数是一种 “逆向” 的函数调用方式,不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时,由另外的代码通过函数指针来触发调用。

2.2 核心价值

  1. 非阻塞:主程序不用傻等(你可以继续玩手机等通知)。
  2. 异步处理:耗时操作完成后自动触发后续动作。
  3. 解耦:点餐系统和取餐动作分离(厨房无需知道你怎么取餐)。

2.3 回调函数的实现机制

举个栗子:

  1. 你点餐(发起请求)
    告诉服务员:“我要一份牛排,做好后叫我取餐(牛排:callback_func,
                                                                                  服务员:register_callback)

  2. 厨房工作(主程序执行)
    厨师开始烹饪,你不需要站在厨房门口等(厨师:void (*callback)(int)回调函数)

  3. 叫你取餐(回调执行)
    牛排做好后,服务员主动通知你:“您的餐好了!”

1.3.1 普通函数指针定义的代码例子实现

void callback_func(int value) {printf("Callback triggered with value: %d\n", value);
}void register_callback(void (*callback)(int)) {int event_value = 42;callback(event_value); // 通过函数指针触发回调
}int main() {register_callback(callback_func); // 传递函数指针return 0;
}

1.3.2 typedef定义的代码例子实现

#include <stdio.h>typedef void (*CallbackFunc)(int);void handleEvent(int value) {printf("Callback triggered with value: %d\n", value);
}void registerCallback(CallbackFunc func) {func(42);
}int main() {registerCallback(handleEvent);return 0;
}

既然我们讲完了回调函数的概念,那么他在嵌入式中有什么使用的地方呢?

2.4 一般使用场景

2.4.1 中断服务程序(ISR)中异步通知用户代码。

// 硬件层定义中断回调类型
typedef void (*isr_callback_t)(void);// 注册回调函数指针
volatile isr_callback_t user_callback = NULL;void register_isr_handler(isr_callback_t cb) {user_callback = cb;
}// 实际中断处理程序
void TIM2_IRQHandler() {if(TIM2->SR & TIM_SR_UIF) {TIM2->SR &= ~TIM_SR_UIF;if(user_callback) user_callback();}
}


2.4.2 硬件抽象层(HAL)中实现驱动与应用的解耦

// HAL库定义UART接收回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {if(huart->Instance == USART1) {// 应用层处理接收完成事件process_received_data(huart->pRxBuffPtr);}
}// 应用层使用示例
uint8_t rx_buffer[32];
HAL_UART_Receive_IT(&huart1, rx_buffer, sizeof(rx_buffer));


2.4.3 协议栈(如TCP/IP)中处理数据到达事件。

// LwIP协议栈定义接收回调
err_t tcp_recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{if(p != NULL) {// 应用层处理网络数据包process_network_packet(p->payload, p->len);pbuf_free(p);}return ERR_OK;
}// 注册回调函数
tcp_recv(tcp_conn, tcp_recv_callback);


2.4.4 定时器模块中定义超时后的自定义行为。

// 定时器模块接口
typedef void (*timer_callback)(void*);struct timer {uint32_t timeout;timer_callback cb;void* user_data;
};// 定时器到期处理
void timer_expire(struct timer* t) {if(t->cb) t->cb(t->user_data);
}// 应用层使用示例
void led_toggle(void* arg) {GPIO_TogglePin(LED_PORT, LED_PIN);
}struct timer led_timer = {.timeout = 1000,.cb = led_toggle,.user_data = NULL
};

3.跳转执行

3.1 跳转执行的概念

在单片机编程中,尤其是涉及到固件更新或者Bootloader设计时,绝对地址跳转是一项重要的技术。在C语言环境中,我们通常不直接操作内存地址,而是通过函数指针来实现这种跳转。

跳转执行他前面的流程其实和回调函数一样,但回调函数执行完还会返回到调用的地方/地址,函数跳转是在跳转到的那个地址执行下去,不会返回了(这是我们的期望结果)

就比如你在main函数执行跳转执行的代码,那他跳转过后不返回,后面就不会就行执行main函数的代码了。(就是离家出走不回来了)

有的人可能很懵逼,主函数都不执行了,那我还能维持整个项目的运行

当然可以的,兄弟,我们只要跳转到的地址里面是另外一个初始化程序+main函数,那么我们就可以执行新的程序的代码了。

3.2 核心价值

  1. 跳转:跳转到另外一个程序(APP)的入口点。
  2. 不返回:执行另外一个程序,那自然就让他执行下去嘛,旧程序的使命就完成了,那就不要回去了。

3.3 跳转函数的实现机制(嵌入式)

目前我基础到的函数跳转的使用就是嵌入式的 STM32 Bootloader 中实现跳转到用户程序(APP)。因此我举例的也是嵌入式的函数跳转例子供大家参考,阅读起来会有点难度。

typedef void (*pFunction)(void);void JumpToApplication(uint32_t appAddr)
{uint32_t stackPointer = *(volatile uint32_t*)appAddr;uint32_t resetHandler  = *(volatile uint32_t*)(appAddr + 4);// 检查栈指针是否在 SRAM 地址范围内(0x20000000 ~ 0x2001FFFF)if ((stackPointer & 0x2FFE0000) != 0x20000000) {printf("Invalid stack pointer in application.\r\n");return;}// 关闭全局中断__disable_irq();// 设置主栈指针 MSP__set_MSP(stackPointer);// 跳转到用户程序入口pFunction jumpToApp = (pFunction)resetHandler;jumpToApp();
}
    步骤操作说明
    1uint32_t stackPointer = *(volatile uint32_t*)appAddr;从 Flash 起始地址读取栈顶地址
    2uint32_t resetHandler = *(volatile uint32_t*)(appAddr + 4);读取复位处理函数地址(即程序入口)
    3__set_MSP(stackPointer);设置主栈指针为用户程序的栈顶值
    4jumpToApp();实际跳转到用户程序运行

    3.4 一般使用场景

    3.4.1 STM32 Bootloader 中实现跳转到用户程序(APP)

    代码如上

    4.总结

    特性callback()(回调函数)Jump_To_Application()(跳转执行)
    是否返回✅ 是❌ 否
    是否跳转到其他程序❌ 否✅ 是
    是否改变程序流❌ 否✅ 是
    是否释放控制权❌ 否✅ 是
    应用场景初始化、检查、配置等跳转到用户程序

    虽然我列了两部分,但你理解的时候可以看成一个知识点来理解。下面给出我的理解方法。

    1.当我们初始化了函数了之后,那肯定是有返回地址的,这个是我们设置了函数的最后会有LR寄存器来保护返回地址。因此能返回回来。

    2.但我们的函数跳转我们只是传入了地址,这个地址是没有定义函数的,没有初始化地址那就没有LR寄存器来保护返回地址。因此无法返回地址。因此只能一直执行下去。

    http://www.lqws.cn/news/452737.html

    相关文章:

  1. 国产芯片能在服务器领域替代Intel(经验总结贴)
  2. Git——分布式版本控制工具
  3. 【MySQL篇07】:redo log日志与buffer pool详解
  4. Vue2 ElementUI Tree 拖动目标节点能否被放置及获取放置位置
  5. 内存的代价:如何正确与 WASM 模块传值交互
  6. 大内存对电脑性能有哪些提升
  7. Docker容器常用命令汇总
  8. 游戏架构中的第三方SDK集成艺术:构建安全高效的接入体系
  9. 16、Redis底层数据结构
  10. 网站如何启用HTTPS访问?本地内网部署的https网站怎么在外网打开?
  11. FPGA--hello
  12. http通信测试,模拟客户端
  13. 【动手学深度学习】4.5. 权重衰减
  14. Hollywood: The World’s Most Effective Propaganda System
  15. 【云创智城】YunCharge充电桩系统源码实现云快充协议深度解析与Java技术实践:打造高效充电桩运营系统
  16. Selenium自动化测试全解
  17. opencv依据图像类型读取图像像素点
  18. 【PyTorch】请问,Reproducibility中的‘:4096:8‘是什么呀?
  19. 20250620-Pandas.cut
  20. aws(学习笔记第四十五课) route53-failover
  21. 文件夹美化工具推荐,打造个性化电脑界面
  22. 【网工】华为配置专题进阶篇④
  23. 学习华为 ensp 的学习心得体会
  24. 10分钟撸出高性能网络服务:吃透高性能优化:缓存_锁_系统调用_编译
  25. 汽车整车厂如何用数字孪生系统打造“透明车间”
  26. 【React】React CSS 样式设置全攻略
  27. DAY 37 早停策略和模型权重的保存
  28. RPGMZ游戏引擎 如何手动控制文字显示速度
  29. 机器翻译与跨语言学习数据集综述
  30. 情感大模型