C语言里面的函数指针

好久没有写博客了,感觉都快忘记这个小站的存在了,但是毕竟还是花了钱的,养着就要用起来,所以还是要坚持写一些文章,这样不仅是对自己技术的一个积累,也是一种良好的习惯。

今天我们来聊一个比较基础的C语言知识,函数指针,指针算是C语言里面一个非常重要的知识了,也是C语言的一个最大特色,并且也可以认为是C语言的万恶之源,因为这玩意学起来真的是有点费劲,并且用不好的话很容易引火烧身,C语言泄漏可谓是防不胜防,连谷歌这样的大公司也是头疼不已,何况我等业余程序员。好了不瞎扯了,我们开始聊我们的正题。

函数指针

简单来说函数指针就是指向函数的指针,听起来是有些废话,但是用起来确实爽的不要不要的,因为借助函数指针,我们就可以像普通变量一样操作函数了,像是各种高级语言里面的面向对象,用C语言的函数指针实现起来也是分分钟的事情呀,各种函数继承什么的用好了也是不在话下呀,听起来是不是很高端?没错,函数指针就是这么牛逼哄哄的。

函数指针类似于普通的指针,区别就是函数指针指向的是一个固定形式的函数,所谓固定形式主要是函数的参数数目,类型以及返回值的类型是固定的,这个也是非常必要的,毕竟编译器需要知道如何使用指针指向的函数,另外我们一般要对函数指针进行赋值或者调用操作,如果类型不对的话也是会出现问题的。

基本使用

在使用函数指针的时候,我们一般会根据目标的函数原型定义一个函数指针的类型,这样可以方便的对指针变量进行定义。当然我们也可以直接对指针进行定义,比如如下的例子:

int add(int a, int b);    // function
int sub(int a, int b);    // function 

int (*pf)(int, int);      // un-initialized function pointer
pf = add;                 // function name is function pointer
pf(23, 45);               // call function via function pointer
pf = sub;                 // change to other function
pf(23, 45);               // use same function pointer call a different func

typedef int (*oper_t)(int,int);         // new type for function
oper_t operation_factory(char *name);   // return a function pointer
int register_oper(char* name, oper_t operation); // as a function parameter
oper_t operations[4];                   // function pointer array

应用案例之回调函数

回调函数(callback function),字面上理解就是函数返回的时候调用的函数,其实实际含义也是类似。嵌入式系统设计中我们常常是用在中断处理函数或者操作系统的任务函数中,比如用户按了一个按键,理论会触发一个中断,其实很多嵌入式的SDK里面会自带一个中断处理函数,整个处理函数就是支持回调,也就是用户在初始化的时候直接将自己的处理函数注册为一个回调函数,这样中断产生的时候,中断函数就可以直接调用用户定义的回调函数。

void register_call_back(handle_t *handle, callback_t callback, void *userData)
{
    /* Set callback and userData. */
    handle->callback = callback;
    handle->userData = userData;
}

上面就是一个注册回调函数的例子,这里实际上不仅注册了回调函数,还注册了一个回调函数中用到的用户自定义的数据作为函数的一个参数,也就是用户还可以为回调函数设置特定的调用参数。因为用户数据的数据形式不尽相同,所以这里并没有固定调用格式,而是直接使用了一个空指针。

所谓的空指针也是C语言的一个特色,也有着特别广泛和高效的使用方法。其实C语言的指针都是一样的,都是指向内存的一个地址,这个地址可以是逻辑地址也可以是物理地址。它代表的就是数据的位置,而指针类型就是告诉编译器我从这个地址以什么样的方式读取数据或者写入数据。所以你看是不是很灵活?但是一旦数据读写的方式不对或者是长度超出范围(编译器会有推断,但是人心难测)就会产生内存泄漏,这个是谁也不想的,但是感觉也是谁也不能避免的。

应用案例之中断向量表

中断向量表也是函数指针的一个比较典型的应用。


typedef void (*p_isr_fun)(void);

static const p_isr_fun __VECTOR_TABLE[] __attribute__ ((used, section(".isr_vector"))) = {
   (void(*)(void)) __StackTop,                  // Initial stack pointer
   _reset_init,                                 // Reset handler
   VECTOR_002,
   VECTOR_003,
   VECTOR_004
   ...
};

其实ARM的中断也是比较简单的,核心就是这个中断向量表,这个中断向量表中定义了各种中断产生的时候程序跳转的位置。所以本质上中断向量表就是一个函数指针数组,CPU根据中断向量号从数组中提取函数进行跳转。当然这种跳转是不支持携带参数的,用户有需要可以自己通过全局变量或者局部变量的方式对外部变量进行读写。

应用案例之面向对象

现在的程序猿最缺啥,缺对象呀,尤其是C语言这种低端语言,更是很难找到对象呀。不过有了函数指针在手,咱们完全不用害怕了,要对象有对象,要接口有借口,要孩子有孩子,要自行车。。。要啥自行车??

struct i2c_driver {
    unsigned int class;

    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
    int (*remove)(struct i2c_client *client);
        struct device_driver driver;
        const struct i2c_device_id *id_table;
};
static struct i2c_driver smbalert_driver = {
    .driver = {
        .name    = "smbus_alert",
    },
    .probe        = smbalert_probe,
    .remove        = smbalert_remove,
    .id_table    = smbalert_ids,
};

瞅瞅,是不是类的定义和例化都有了?完不完美,这里借鉴的是Linux Kernel的部分代码,感觉阅读大神的代码还是很有收获的。读过才知人和人之间的差距,不写了,搬砖去了,这篇就先这样吧!


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

相关文章

发表新评论