“ 忠诚、笃学、严谨、守纪 ”

点击任意处进入
↓

为什么上期被隐藏了

胡曼不是一个人,是一种状态

Day 4 函数指针与回调

1. 函数指针

  • 普通指针:存的是变量的地址,比如 int *p,指向一块存 int 的内存。

  • 函数指针:存的是函数的入口地址,指向代码段里的函数,不是数据段。

函数本身有类型:返回值 + 参数列表。函数指针必须和它完全对上。

// 声明 fp 是一个函数指针

// 它指向:返回 int、吃两个 int 的函数

int (*fp)(int, int);

typedef 用来给类型起别名

typedef 返回值 (*类型名)(参数列表);

2. 函数回调

回调:把一个函数指针当参数传给另一个函数,由那个函数在内部调用它。

接收方不写死逻辑,逻辑由外部传进来的回调决定。

#include <stdio.h>

typedef int (*OpFunc)(int, int);

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

// calc:通用调度函数,op 是回调
int calc(int x, int y, OpFunc op)
{
    if (op == NULL) return 0;      // 防御:万一传了空指针
    return op(x, y);
}

int main(void)
{
    printf("add: %d\n", calc(4, 5, add));
    printf("sub: %d\n", calc(4, 5, sub));
    printf("mul: %d\n", calc(4, 5, mul));
    return 0;
}

qsort 标准库(C 语言最经典的回调案例):

头文件:#include <stdlib.h>

原型:

void qsort(void *base,
           size_t nmemb,
           size_t size,
           int (*compar)(const void *, const void *));

拆解参数:

  1. base:数组首地址。void * 是无类型指针,任何类型的数组都能传

  2. nmemb:元素个数

  3. size:单个元素占几字节

  4. compar:比较回调

综合练习

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int add(int a,int b){return a + b;}

int sub(int a,int b){return a - b;}

int mul(int a,int b){return a * b;}

typedef int (*replace)(int,int);


int call(int x,int y,replace op){
    if(op == NULL){return 0;}
     return op(x,y);
}

int cmp_int(const void *a,const void *b){
    const int *pa = (const int*)a;
    const int *pb = (const int*)b;
    if(*pa < *pb){return -1;}
    if(*pa > *pb){return  1;}
    if(*pa == *pb){return  0;}
}

int cmp_str(const void *a,const void *b){
    const char *const *pa = (const char *const*)a;
    const char *const *pb = (const char *const*)b;

    return strcmp(*pa,*pb);
}


int main(){
    int (*fp)(int,int);
    fp = add;
    printf("test 6+5 = %d\n",fp(6,5));

    replace su;
    su = sub;
    printf("test 6-5 = %d\n",su(6,5));

    replace mu;
    mu = mul;

    printf("====c测试回调====\n");
    printf("78 + 91 = %d\n",call(78,91,fp));
    printf("78 - 91 = %d\n",call(78,91,su));
    printf("78 * 91 = %d\n",call(78,91,mu));


    printf("====qort回调排序int数组====\n");
    int arr[] = {9,1,8,2,5};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("排序前:");
    for (int i = 0; i < 5; i++)
    {
        printf("%d ",*(arr+i));
    }
    printf("\n");

    qsort(arr,n,sizeof(int),cmp_int);
    printf("排序后:");
    for (int j = 0; j < 5; j++)
    {
        printf("%d ",*(arr+j));
    }


    printf("====字符串排序====\n");
    char *str1[] = {"banana","apple","orange","watermelon"};
    int nstr = sizeof(str1) / sizeof(str1[0]);
    printf("排序前:\n");
    for (int i = 0; i < 4; i++)
    {
        printf("%s ",str1[i]);
    }
    printf("\n");

    qsort(str1,nstr,sizeof(str1[0]),cmp_str);
    printf("排序后:\n");
    for (int i = 0; i < 4; i++)
    {
        printf("%s ",str1[i]);
    }
    printf("\n");

    return 0;
}

3.简易分发表(switch的替代品)

思想:用结构体数组存「匹配 key + 函数指针」,循环查表,命中就调用对应函数。

替代冗长的 if-else if / switch case。协议解析、命令分发、opcode 处理到处都是。

typedef int (*CmdFunc)(void);
typedef struct {
    int         cmd_id;   // 命令码 / opcode
    CmdFunc     handler;  // 对应的处理函数
    const char *name;     // 便于调试和打印
} CmdEntry;

完整练习

#include <stdio.h>

typedef int (*CmpFunc)(void);
typedef struct 
{
    int cmd_id;
    CmpFunc handler;
    const char *name;
}Cmdentry;

int cmd_hello(void){
    printf("hello command\n");
    return 0;
}
int cmd_f8(void){
    printf("fei 8 fen qian\n");
    return 0;
}
int cmd_test(void){
    printf("a test command\n");
    return 0;
}
int cmd_exit(void){
    printf("exit\n");
    return 1;
}

static const Cmdentry commandtest[] = {
    {0x01,cmd_hello,"HELLO"},
    {0x02,cmd_f8,"F8"},
    {0x03,cmd_test,"TEST"},
    {0x04,cmd_exit,"EXIT"},
    {0x99,NULL,     NULL   },
};

int dispatch(int cmd_id){
    int i;
    for(i=0;commandtest[i].handler != NULL;i++){
        if (commandtest[i].cmd_id == cmd_id)
        {
            return commandtest[i].handler();
        }
    }
    printf("unknown cmd_id: 0x%02X\n",cmd_id);
    return -1;    


}

int main(void)
{
    dispatch(0x01);
    dispatch(0x02);
    dispatch(0x03);
    dispatch(0x04);
    dispatch(0x99);
    return 0;
}

为什么用哨兵(最后一行 handler = NULL):这样遍历时不用另外传一个"数组长度"参数,表加长缩短都不用改 dispatch。真实项目里两种都常见:哨兵,或者额外传 count。

逆向里看到一大片"整数 + 函数地址"交替出现的数组,几乎都是这种分发表。IDA 解析出来就是跳转表。

优点:新增命令不用改 dispatch,只需往数组加一行。

4. GDB验证

编译:gcc fp.c -g -o fp(Windows 上产物是 fp.exe,下面命令里的程序名相应带上)

b main
run
p fp                      # 打印函数指针里存的地址
p fp(3,4)                 # 直接在 GDB 里调用
p cmd_table[0].handler    # 看表项里存的函数地址
p cmd_table[0].name
disass cmd_hello          # 反汇编回调本体

部分调试:

(gdb) b dispatch
Breakpoint 2 at 0x4015d7: file D:\Dxt\c-learning\day4\day4_2.c, line 43.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: D:\Dxt\c-learning\day4\day4_2.exe
[New Thread 55292.0x987c]
[New Thread 55292.0x142f4]

Thread 1 hit Breakpoint 1, main () at D:\Dxt\c-learning\day4\day4_2.c:57
57          dispatch(0x01);
(gdb) break dispatch
Note: breakpoint 2 also set at pc 0x4015d7.
Breakpoint 3 at 0x4015d7: file D:\Dxt\c-learning\day4\day4_2.c, line 43.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) n
Program not restarted.
(gdb) p cmd_id
No symbol "cmd_id" in current context.
(gdb) n

Thread 1 hit Breakpoint 2, dispatch (cmd_id=1) at D:\Dxt\c-learning\day4\day4_2.c:43
43          for(i=0;commandtest[i].handler != NULL;i++){
(gdb) p cmd_id
$1 = 1
(gdb) n
44              if (commandtest[i].cmd_id == cmd_id)
(gdb) p cmd_id
$2 = 1
(gdb) n
46                  return commandtest[i].handler();
(gdb) n
hello command
53      }
(gdb) n
main () at D:\Dxt\c-learning\day4\day4_2.c:58
58          dispatch(0x02);
(gdb) n

Thread 1 hit Breakpoint 2, dispatch (cmd_id=2) at D:\Dxt\c-learning\day4\day4_2.c:43
43          for(i=0;commandtest[i].handler != NULL;i++){
(gdb) p cmd_id
$3 = 2
(gdb) p commandtest[i]
$4 = {cmd_id = 1, handler = 0x401550 <cmd_hello>, name = 0x404031 "HELLO"}
(gdb)

要多理解

posted @ 2026-10-28 21:58  alonep  阅读(5)  评论(0)    收藏  举报
↑