百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

【C语法硬核20讲】08 函数指针:回调与状态机

myzbx 2025-09-04 13:54 6 浏览

目标:掌握 函数指针(function pointer) 的声明/赋值/调用、**回调(callback)**设计模式、表驱动(table-driven)与有限状态机(finite state machine, FSM) 的实战写法,以及类型不匹配与上下文传递的避坑。


1)基础语法速记

int add(int,int);
int (*fp)(int,int) = add;   // 声明并赋值
int r = fp(3,4);            // 调用

建议用 typedef 降低心智负担

typedef int (*binop_t)(int,int);
int sub(int a,int b){ return a-b; }
binop_t op = sub;

2)标准库里的回调案例:qsort

int cmp_int(const void *a,const void *b){
    int x = *(const int*)a, y = *(const int*)b;
    return (x>y) - (x<y);
}
qsort(arr, n, sizeof arr[0], cmp_int);

签名必须完全匹配。强转错误签名是 未定义行为。


3)回调 + 上下文(context)传递模式

很多 API 只给你一个 void* 存上下文。

typedef void (*on_event_t)(void *ctx, int code);

struct Handler {
    void       *ctx;
    on_event_t  on_event;
};

void dispatch(struct Handler *h, int code){
    if (h->on_event) h->on_event(h->ctx, code);
}

好处:回调可无状态(把状态放 ctx),也可复用同一个处理函数配不同上下文。


4)表驱动:用函数指针数组消灭 switch-case

typedef int (*uop_t)(int);

static int op_inc(int x){ return x+1; }
static int op_dec(int x){ return x-1; }
static int op_neg(int x){ return -x; }

static uop_t OPS[] = { op_inc, op_dec, op_neg };

int apply(int which, int x){
    if (which < 0 || which >= (int)(sizeof OPS/sizeof OPS[0])) return x;
    return OPS[which](x);
}

扩展性:新增操作只需新增一个函数并放进表。


5)有限状态机(FSM)两种写法

A.表驱动 FSM(状态 × 事件 → 处理器)

typedef enum { S_INIT, S_OPEN, S_CLOSED, S_N } state_t;
typedef enum { E_OPEN, E_CLOSE, E_TIMEOUT, E_N } event_t;

typedef state_t (*handler_t)(void *ctx);

struct Cell { handler_t h; };

state_t on_init_open(void*), on_open_close(void*), on_any_timeout(void*);

static struct Cell T[S_N][E_N] = {
/*           OPEN            CLOSE              TIMEOUT */
 [S_INIT]  ={ on_init_open,  NULL,              on_any_timeout },
 [S_OPEN]  ={ NULL,          on_open_close,     on_any_timeout },
 [S_CLOSED]={ NULL,          NULL,              on_any_timeout },
};

state_t dispatch(void *ctx, state_t s, event_t e){
    handler_t h = T[s][e].h;
    return h ? h(ctx) : s;
}

优点:结构清晰,易于审计与单测。

B.状态函数式(状态函数返回下一个状态)

typedef state_t (*state_fn)(void *ctx, event_t e);

state_t st_init(void *ctx, event_t e){
    switch(e){ case E_OPEN: /* ... */ return S_OPEN; default: return S_INIT; }
}

变体:用 state_fn 数组 state_fn STATES[S_N],每个状态有一个处理函数。


6)回调注册 API 的设计模板

typedef void (*cb_t)(void *ctx, int evt, const void *data);

struct Bus {
    cb_t    cb;
    void   *ctx;
};

void bus_subscribe(struct Bus *b, cb_t cb, void *ctx){
    b->cb = cb; b->ctx = ctx;
}
void bus_emit(struct Bus *b, int evt, const void *data){
    if (b->cb) b->cb(b->ctx, evt, data);
}

注意:API 里始终把上下文放第一参数,形成统一风格。


7)避坑与性能

  • 签名必须匹配(参数与返回值类型);强转只是抹掉类型检查,不能修正不匹配
  • 不要把带 ... 可变参数的函数指针混用到固定参数签名。
  • 热路径频繁小回调:可改为函数指针表 + static inline 包装,或直接开关宏避免间接跳转开销。
  • 嵌入式/ISR:有的平台对函数指针调用有额外开销或限制,需基准测试。
  • 与线程:回调里修改共享状态需加锁或用 _Atomic。

8)自检清单

  • 会写 typedef 简化声明
  • 会传 void* ctx 做上下文
  • 会用函数指针数组实现表驱动
  • FSM 能用“表”或“状态函数”两种模式落地
  • 知道签名不匹配是 UB,绝不强转“糊弄”

9)迷你练习

1)把 qsort 的比较器改为“降序”。
2)为一个简易解析器设计状态表:S_NUM/S_OP/S_ERR × 事件 E_DIGIT/E_PLUS/E_OTHER。
3)把日志后端做成回调:可注册“写到文件”或“写到 socket”。


小结

  • static/extern:管可见性与生命期
  • 宏:用全括号、do-while(0)、无副作用,该用 static inline 就用;
  • 函数指针:用在回调、表驱动、状态机,签名要一丝不苟,上下文用 void* 传递。

相关推荐

微信又双叒叕更新了!这次是安卓版

澎湃新闻综合报道近日安卓版微信正式更新了8.0.10版主要有四大更新日常使用起来会更加方便一起来看看吧1朋友圈视频封面在此之前,朋友圈背景一直只能放静态图片,但此次更新后,可以从视频号中选择一段...

镜子里的你和照片里的你,哪个更真实?

不知道大家有没有这样的经历。聚餐、团建……一群人拍合照,拍完之后,我们满心期待地放大照片,却惊慌失措地发现——怎么自己又被拍得这么丑!但这时,别人总是会说道——「这就是你平常的样子啊。」可是,我们平时...

歼20战斗机现身珠海,首次公开静态展示,体现解放军的自信和强大

日本航空自卫队在9月份举行了三泽基地开发日活动,期间出动12架F-35A闪电II战斗机进行了公开展示,不过仅仅是编队通场飞过而已。日本航空自卫队仅仅动用1架F-35A战斗机进行了机动飞行表演,从公开的...

Java类初始化阶段深度解析:执行顺序与线程安全

一、初始化阶段核心机制二、分步详解与代码验证1.初始化触发条件主动使用场景:publicclassInitTrigger{static{System.out.pr...

深入剖析 Java 类加载机制:原理、优化与实践

作为Java开发者,你是否遇到过这样的场景:线上服务突然抛出NoClassDefFoundError,但本地调试却一切正常;或者明明引入了依赖JAR,却始终报ClassNotFoundExcep...

SUID/SGID是啥?如何让普通用户拥有root的能力?

原文链接:「链接」在Linux系统中,权限控制是一项至关重要的安全机制。除了常见的r(读)、w(写)和x(执行)权限外,还有三种特殊权限位常被忽视:SUID(SetUserID)、SGID...

数码宝贝新世纪:SP奥米加兽AS情报泄露,是否也是强力辅助?

大家好!我是小飉[liáo],欢迎来阅!情怀手游《数码宝贝新世纪》官方不按套路出牌,这次公布的入围测试的人员名单,但是并没有公布SP奥米加兽AS的能力情报,还好广大网友给力。次日,在论坛,以及...

抽象类(abstract class)与接口(interface)

A.核心概念1.抽象类-定义:带有abstract修饰符的类,不能被实例化,用于定义一组方法签名和可选的部分公共实现。-特性:-可以包含字段、构造函数、已实现的方法(带方法体)和抽象方法(...

S39结束时间确定,新赛季段位继承公布,大量皮肤在7月初集体上线

文/静海君如果说之前都还是猜测的话,那游戏内的一个变动,基本100%确定了新赛季(S40)的开启时间。新赛季的开启时间关于新赛季的开启时间,目前主要有两个线索。第一个关于新赛季开启时间的线索是「游戏内...

一篇文章掌握整个JVM,JVM超详细解析!!!

不懂JVM看完这一篇文章你就会非常懂了,文章很长,非常详细!!!先想想一些问题1我们开发人员编写的Java代码是怎么让电脑认识的首先先了解电脑是二进制的系统,他只认识01010101比如我们经常要...

项目用 JDK17 后,bug 少了、速度快了!这 4 个好处太实在

别再死守JDK8了!去年把电商项目升级到JDK17,团队直接爽翻:代码量少写1/3,大促再也不卡顿,运维半夜不call人,连测试都夸bug少了。今天就说真话,JDK17在项目里的4...

法定继承有顺序:在法定继承人中,谁应该优先继承?

免费问律师_法律咨询免费24小时律师在线解答-法临网“父母去世没留遗嘱,兄弟姐妹争遗产闹上法庭!”法定继承中,谁优先拿财产?《民法典》明确“顺序+份额”规则,一文说清关键点,避免家庭内耗!一、法定...

前端必会:ES5寄生继承 vs ES6 Class继承

大家好,我是谦!说到继承,估计不少前端开发者都踩过坑。尤其是在ES5到ES6的过渡阶段,我们写代码时常常被问到:“你用的是原型继承还是Class继承?”再加上面试官特别喜欢追问底层实现——...

子女入了外籍能否继承父母国内的房产呢?

大家好,这里是家理范律,专注遗产继承、婚姻家事领域!-很多加入外籍的朋友都纠结:自己还能继承国内父母的房产吗?答案是可以继承,但流程远比想象复杂!-真实案例:美籍华人张先生,拿着父母在加州公证的遗嘱回...

J.A.C.S | 基于化学类型和靶点的基因组挖掘以寻找一种新的细菌肽脱甲酰酶天然产物抑制剂

大家好,今天推送的文章是2025年6月发表在JournaloftheAmericanChemicalSociety上的“Chemotype-andTarget-DrivenGenome...