前言
最近在学操作系统,实验课是阅读操作系统 μ C / O S − I I \mu C/OS- II μC/OS−II 的源码,涉及到了一些指针的高级应用,于此做简要整理,并不断更新。
指针部分
void * 和 NULL的区别?
让我们看看编译器对NULL 的定义
宏定义有点难读,我觉得大概就是,NULL 的值在大部分情况下是 0, 而不是(void *)0,图中外面的大括号是为了保证整体性,防止宏替换时发生错误。那么 NULL 和 (void *)0 的区别是啥呢?
我写了下面这个程序:
编译器报错:
[Error] invalid conversion from ‘void*’ to ‘int*’ [-fpermissive]
也就是说(void *)0 是一个指针(可以理解为指向0地址的指针常量),无参数类型的指针,需要加类型强转才能赋值给(int *)。而NULL通常情况下等于0。
所以我们通常写的int *p = NULL
其实等价于int *p = 0
,也就是p指向0地址。
举一反三:(void *)不能直接赋值给(int *)那反之可以吗?答案是肯定的。
void * 可以看作是所有指针类型的鼻祖, 祖先可以包容后代,而后代不可以包容祖先。(
真是一代不如一代)
有点类似于面向对象中的向上转型。
根据这个性质,在阅读源码时就能理解为啥函数参数为(void *)类型了。
··数据类型多种多样,可能是各种结构体,这时候传个指针就很方便的操作任意类型的数据。
回调函数、函数参数
回调函数是什么,有什么用?
举个例子:比如我们想写一个程序当 a > b 时 ,交换a , b
(虽然下面的程序完全不必要写的这么麻烦)
bool Bigger(int a, int b){
return a > b;
}
void SwapAB(int a, int b){
if(Bigger(a,b))
swap(a,b);
}
过了一会,我们发现逻辑想错了,应该是a < b 交换 a, b
bool Smaller(int a, int b){
return a < b;
}
void SwapAB(int a, int b){
if(Smaller(a,b))
swap(a,b);
}
观察上述过程,我们发现,在修改时,改了SwapAB的代码,又添加了Smaller函数,代码修改量大。而且在实际开发中,SwapAB的代码往往是透明的,无法直接修改。为了解决这个问题,函数参数与回调便出现了。
看下面代码,SwapAB中有一个函数参数,这个函数参数的名字是cmp(cmp其实是函数指针,指向函数的真正地址),返回值必须是bool类型,而且这个函数参数的参数必须是(int , int )。
bool cmp(int a, int b){
return a < b;
}
void SwapAB(int a, int b, bool (*cmp)(int, int )){
if(cmp(a,b))
swap(a,b);
}
这样做有什么好处呢?当我们想再次修改cmp时,就不需要改动SwapAB的代码,而是直接改 cmp 函数就可以了。
这种把函数作为参数传递到主函数里面,主函数再调用的过程,就叫做回调。
看一下操作系统源码,果不其然,MyTask函数作为参数传进了OSTaskCreate()函数。
- 函数指针变量
阅读一下结构体的定义,cmp作为一个函数指针变量存在,这是以前没有见过的。
struct {
int a;
int b;
bool (*cmp) (int, int);
}