1. 说明
在 C 语言编程中, 指针是最容易出错的地方, 尤其是在很多指针同时出现的时候, 看的眼花缭乱的, 本文从嵌入式中常用的复杂角度进行分析, 彻底搞清楚 C 语言中的容易弄错的指针使用问题.
2. 函数指针与指针函数
在 C 语言中, 函数是有他的地址, 同理, 函数有也有他的地址, 如果如果我们把函数的地址赋值给函数指针, 那么我们就可以间接的通过函数指针调用函数地址了.
函数指针的定义如下:
数据类型 (fun)(参数列表);
由于 () 的优先级高于.
指针函数的定义如下:
数据类型 fun(参数列表);
其返回值为数据类型.
实例: 通过函数指针调用函数指针
第一步: 定义函数指针
int(pfun)(int,int);
这里调用了一个数据类型为 int 的函数指针, 其中两个参数为两个 int.
第二步: 定义指针函数
intfun(int, int);
这里函数的返回值是 int.
第三步: 实现函数指针
- intfun(int a, intb)
- {
- int ret = 0;
- (ret) = (a) + (*b);
- return ret;
- }
第四步: 把函数的地址赋值给函数指针
- int main(int argc, char*argv)
- {
- int a = 3, b = 2,c = 0;
- pfun = fun;
- c =((pfun)(&a,&b));
- rt_kprintf("c is %d\n", c);
- return0;
- }
其中最关键的是赋值和调用, 赋值时采用的是 pfun = fun;, 而间接调用函数时采用的是((*pfun)(&a,&b));.
3.const 修饰的指针问题
首先看一下下面的语句:
- constint p;
- intconstq;
- int const r;
- constint const x;
在进行 C 语言编程时, 经常会用 const 来修饰一个变量, 这样阻止一个变量被改变.
前面两个 const int p; 与 int constq; 表达的含义一样, p 和 q 都被申明为 const int 类型的指针. 也就是说, 在程序中, 不可以修改 p 和 q 的值. 为了阅读便利, 通常采用 const 在前面的方式.
- int a = 3, b = 2;
- constint p = &a;
- p = &b;p = 5;//err
- rt_kprintf("p is %d\n",p);
其中 * p 的值不可以被修改, 但是 p 的值是可以被修改的.
对于 int *const r;
- int const r = &a;
- r = &b;//errr = 6;
- rt_kprintf("r is %d\n",*r);
其中 r=&b 是错误的.
结合上述操作, 得到 const int * const x = &a;. 这个是需要在使用的时候进行赋值, 而且不可以修改, 也就是
- x = &b;//err
- *x = 6;//err
这些操作都是错误的.
4. 函数指针直接跳转的问题
我们在真实的项目开发过程中, 可能需要直接跳转到函数的某个地址去指针.
- void (function_p)(void); // 定义函数指针 function_p, 无返回值, 无参数
- function_p = my_func; // 函数指针指向 function 函数
- (function_p)(); // 采用函数指针运行函数
这个等同于直接调用 my_func 函数, 那么这个有什么意义呢?
其实这样提出了一个思路, 就是可以根据函数的地址, 跳转到函数中. 比如我们在 bootloader 中, 当把二进制文件加载到内存中后, 如何去执行这个 kernel 程序呢? 也就是实现一个 bootloader 到 kernel 的跳转.
((void(*)())0x80000)();
这里就是说 0x80000 处的地址是函数类型, 并且没有返回值. 当我们的 kernel 地址为 0x80000 时程序跳转过去, 不再返回. 这就是一个比较经典的例子.
5. 回调函数
回调函数可以说是 C 语言对函数指针的高级应用. 简而言之, 回调函数就是通过函数指针调用的函数. 也就是说我们把函数的指针通过函数参数传递给函数使用, 这时我们就可以认为被调用的函数是回调函数.
我们来分析一个 rt-thread 中具体例子, 来分析回调函数的妙用.
用过 rt-thread 操作系统的人都知道, rt-thread 采用了设备驱动框架, 也就是开发的过程中可以采用虚拟文件系统的操作对驱动设备进行操作. 看一下 rt_device 结构体内容.
- /**
- Device structure
- */
- struct rt_device
- {
- struct rt_object parent;/*<inherit from rt_object/
- enum rt_device_class_type type; /< device type */
- rt_uint16_t flag; /*< device flag/
- rt_uint16_t open_flag; /< device open flag */
- rt_uint8_t ref_count; /*< reference count/
- rt_uint8_t device_id; /*< 0 - 255/
- /device call back/
- rt_err_t (rx_indicate)(rt_device_t dev, rt_size_t size);
- rt_err_t (tx_complete)(rt_device_t dev, void *buffer);
- #ifdef RT_USING_DEVICE_OPS
- conststruct rt_device_ops ops;
- #else
- / common device interface /
- rt_err_t (init) (rt_device_t dev);
- rt_err_t (open) (rt_device_t dev, rt_uint16_t oflag);
- rt_err_t (close) (rt_device_t dev);
- rt_size_t (read) (rt_device_t dev, rt_off_t pos, voidbuffer, rt_size_t size);
- rt_size_t (write) (rt_device_t dev, rt_off_t pos, constvoidbuffer, rt_size_t size);
- rt_err_t (control)(rt_device_t dev, int cmd, voidargs);
- #endif
- #if defined(RT_USING_POSIX)
- conststruct dfs_file_ops *fops;
- struct rt_wqueue wait_queue;
- #endif
- void *user_data; /**< device private data */
- };
其中我们重点分析下面回调函数的接口.
- rt_err_t (rx_indicate)(rt_device_t dev, rt_size_t size);
- rt_err_t (tx_complete)(rt_device_t dev, void *buffer);
第一个函数就是说底层设备接收到数据时, 可以调用这个回调函数, 上层应用实现这个接口即可.
第二个接口也是底层接口调用上层应用层接口的例子.
根据 rt-thread 的设备编程模型
第一步: 找到设备
rt_device_find
返回一个 rt_device_t 类型的设备句柄.
第二步: 实现 rx_indicate 函数
xxx_dev->rx_indicate = xxx_rx_indicate;
其中 xxx_rx_indicate 就是我们需要实现的函数, 这里可释放信号量, 告知其他线程有消息到来.
第三步: 底层调用接口
dev->rx_indicate(dev,size);
有消息到来时, 调用该接口, 上层应用如果实现了这个接口, 就会执行该函数, 如果没有实现, 可以判断 dev->rx_indicate 为空, 不执行.
这样, 程序实现降低耦合性调用的问题. 如果我们直接调用函数, 那么程序设计中耦合性太强, 这个也是 rt-thread 利用回调函数降低耦合性的一个经典例子.
6. 总结
好好理解指针使用对于 C 语言编程非常重要, 磨刀不误砍材工, 只有把基础打好, 上层建筑才能稳固. 也只有基础不断的积累, 不断的总结, 思想境界才能有所提高. 程序设计不仅仅是口头功夫, 也不是两三个月的快速入门能够熟练掌握, 需要日积月累, 不积跬步, 无以至千里, 不积小流, 无以成江海. 以此自勉. 合作微信 17727800897
来源: http://www.bubuko.com/infodetail-3510793.html