回调函数的适用场景及原理&&可变参函数的实现原理

155 阅读3分钟

本文旨在记录对回调函数及可变参函数的一些理解,不恰当的地方欢迎指正

引用:可变参数使用误区回调函数基础原理

简介

文章内容分为回调函数和可变参函数的原理分析和基础实现

回调函数

首先所谓回调函数,是用户将自己的函数实现交给第三方接口,拿常用的定时器举个例子:

void sys_timer_add(void *_para, void (*callback)(void *para), u32 time)
{
    while (time--) {
        do something;
    }
    if (_para && callback) {
        callback(_para);
    }
}

typedef struct {
    char *str;
    short num;
    char type;
} CALLBACK;

void CallBackFunc(void *_para)
{
    CALLBACK *callback = (CALLBACK *callback)_para;
    do something;
}

CALLBACK callback;
u32 time;
//给callback、time赋值后,作为参数输入
sys_timer_add((void *)&callback, CallBackFunc, time)

以上sys_timer_add函数是我们调用的库函数,函数的内部有一个callback分支,当我们有自己的回调函数时会在sys_timer_add内部触发

另外回调函数在传参时最好使用无类型(void)数据,也就是上面代码中CallBackFunc传到sys_timer_addcallback参数会通过(void *)强转,最后在回调函数CallBackFunc中转回实际类型,这样的作用是让sys_timer_add有更强兼容性;上述sys_timer_add函数只是用来举例,实际代码并非如此

按我的理解,回调函数的一个重要的作用就是让用户在不接触库代码的情况下,可以让原本库里面执行流程多出一个可变化的代码,用户通过自定义回调函数让其在库里面的某个位置执行

可变参函数

最常见的可变参函数是printfscanf,该功能实现的基础是va_startva_argva_end三个宏,定义在标准库stdarg.h中,参考下图中的注释了解这三个宏的作用

image.png

这里给出一个简单的例子:

typedef void (*FUNC)(int i, char c, float f, double d, char *str);
void VarArgFunc(char *arg, ...)
{
    va_list pArgs;
    va_start(pArgs, arg);
    FUNC func = va_arg(pArgs, FUNC);
    int i = va_arg(pArgs, int);
    char c = va_arg(pArgs, int);
    float f = va_arg(pArgs, double);
    double d = va_arg(pArgs, double);
    char *str = va_arg(pArgs, char *);
    
    /* (*func)(i, c, f, d, str); */
    func(i, c, f, d, str);
    printf(">>>>> 0x%x  0x%x  0x%x\n", func, (*func), &(*func));
    /* void *v = va_arg(pArgs, (char *)); */
    va_end(pArgs);
}
void custem_func(int i, char c, float f, double d, char *str)
{
    printf("int i:%d\n", i);
    printf("char c:%c\n", c);
    printf("float f:%f\n", f);
    printf("double d:%f\n", d);
    printf("char *str:%s\n", str);
}

int main()
{
    printf("start Callback_VarArgFunc!\n");
    
    int i = 10;
    char c = 'y';
    double f = 12.0123;
    double d = 23.56;
    char *str = "this is a test!";

    VarArgFunc("ifdcv\n", custem_func, i, c, f, d, str);
    
    printf("finish Callback_VarArgFunc!\n");
    return 0;
}

这里VarArgFunc是可变函数名,填入参数包括回调函数地址、若干不同类型变量,最后通过回调函数将不同变量打印出来

这里有几点需要注意的是:va_arg宏虽然可以将传入的char、short、float类型解析出来,但类型需要用int、double,具体原因可以看看文章开头引用的文档;此外要精确地解析出变量个数,需要类似printf那种在固定参数里去判断,va_arg好像并不能判断当前是否是最后一个参数

这里我有一个疑惑:在VarArgFunc函数实体里,为什么得到的func函数,func、 (*func)、 &(*func)打印出的三个值是一样的?

以下贴出可变参函数用到的代码实例:

Callback_VarArgFunc.c

#include "Callback_VarArgFunc.h"
#include "stdarg.h"
#include "stddef.h"

typedef void (*FUNC)(int i, char c, float f, double d, char *str);

void VarArgFunc(char *arg, ...)
{
    va_list pArgs;
    va_start(pArgs, arg);
    FUNC func = va_arg(pArgs, FUNC);
    int i = va_arg(pArgs, int);
    char c = va_arg(pArgs, int);
    float f = va_arg(pArgs, double);
    double d = va_arg(pArgs, double);
    char *str = va_arg(pArgs, char *);
    
    /* (*func)(i, c, f, d, str); */
    func(i, c, f, d, str);
    printf(">>>>> 0x%x  0x%x  0x%x\n", func, (*func), &(*func));
    /* void *v = va_arg(pArgs, (char *)); */
    va_end(pArgs);
}

Callback_VarArgFunc.h

#ifndef __CALLBACK_VARARGFUNC__
#define __CALLBACK_VARARGFUNC__

#include "string.h"

typedef unsigned short u16;
typedef unsigned char  u8;

void VarArgFunc(char *arg, ...);

#endif

main.c

#include "string.h"
#include "stdarg.h"
#include "Callback_VarArgFunc.h"

void custem_func(int i, char c, float f, double d, char *str)
{
    printf("int i:%d\n", i);
    printf("char c:%c\n", c);
    printf("float f:%f\n", f);
    printf("double d:%f\n", d);
    printf("char *str:%s\n", str);
}

int main()
{
    printf("start Callback_VarArgFunc!\n");
    
    int i = 10;
    char c = 'y';
    double f = 12.0123;
    double d = 23.56;
    char *str = "this is a test!";

    VarArgFunc("ifdcv\n", custem_func, i, c, f, d, str);
    
    printf("finish Callback_VarArgFunc!\n");
    return 0;
}

Makefile

exe: Callback_VarArgFunc.c main.c
	gcc Callback_VarArgFunc.c main.c -o exe