二维指针*(void **)的研究(uC/OS-II案例)
uC/OS-II内存管理函数内最难理解的部分就是二维指针,本文以图文并茂的方式对二维指针进行了详细分析与讲解。看完本文,相信对C里面指针的概念又会有进一步的认识。
一、OSMemCreate( ) 函数中有如下语句:
OS_MEM *pmem;
INT8U *pblk;
void **plink;
INT32U i;
plink = (void **)addr; //指向所申请内存分区的起始地址
pblk = (INT8U *)addr + blksize; //所申请内存的第二个Block的起始地址
for (i = 0; i (nblks - 1); i++) //依次申请nblks个Block,链接成单向链表
{
*plink = (void *)pblk;
plink = *plink;
pblk = pblk + blksize;
}
红色部分是初学者对本函数最难理解的部分,因为其用到了二维指针。二维指针就是指向指针的指针,他的内容是一个目标变量的地址,也就是说仍然是一个指针,对二维指针取两次内容才能取到目标变量的内容。这里先复习一下指针的知识:
int a = 5;
int *ptr;
ptr=a; //ptr指针是地址,指向变量a所在地址。
则可以得出:
*ptr=a; //即指针ptr指向变量a所在地址,*ptr的值就是a的值5
由此类推,对于二维指针变量**plink,指针plink是地址,*plink是plink所指向的地址内的数据,不过同时这个数据也是一个指针,并且**plink是指针(*plink)所指向的地址内的数据。
一维指针所指向的地址内存放的是普通数据,如上述ptr指针所指向的地址内存放的是int型数据5。
二维指针所指向的地址内存放的是一个一维指针,如上述指针plink所指向的地址内存放的是指针*plink。
下面详细分析上述OSMemCreate函数内的语句:
1、plink = (void **)addr;
addr本来是一个一维指针,指向所申请内存分区的起始地址。本句将addr强制转换为二维指针(注意经过强制转换后addr指针本身指向的地址是没有变化的),并将addr地址值赋给plink。则plink的内存中存储的是addr的值,即plink也指向addr所指向的分区起始地址,并且这个地址内存放的内容是指针(*plink),但指针(*plink)还未指向具体的地址。在这条语句之前,这个起始地址内的数据内容是未知的(由编译器分配的)。内存分区结构见下图1所示(假设申请的内存区有4个Block)。
在这个函数当中,我们想把addr指向的二维数组,分割成大小相同的若干块,并用指针把它们链接起来,链接指针放在每个block的首地址。但由于addr是一维指针,它指向的内容不会被解释成一个地址,而是一般的内容。我们要在这些block的首地址内存放指针,所以将addr强制转换成二维指针的目的就是让编译器将addr指向的内容解释成地址,也就是一个指针。
再将addr赋值给plink(让plink去执行连接的操作),使plink与addr指向同一个地址。*plink就是取plink与addr指向地址单元的内容,而这个内容是一个指针,也就是在以前addr指向的地方放上指针*plink。
2、pblk = (INT8U *)addr + blksize;
让pblk指向所申请内存的第二个Block的起始地址,见下图2所示。
因为addr是void型的,要强制转换为INT8U型。
3、*plink = (void *)pblk; //实际上是*plink = pblk,因为pblk是INT8U型的,要强制转换为void型
在for循环内对二维指针plink执行取内容操作(其内容为指针), *plink也是一个指针了(plink指针所代表的地址的内容),将下一个block的首地址赋值给*plink,使它指向的地方改为下一个blcok开始的地址处。
起始地址内的指针*plink被赋值为pblk,所以*plink与pblk一样指向下一个blcok开始的地址处。
如图3所示。
第一个block首地址内的内容为一个指针,该指针指向下一个block首地址。这个地址内存放的就是*plink的内容**plink。
只不过我们并不需要用到这个**plink。
4、plink = (void **)pblk;
功能与plink = (void **)addr 相似,即plink也指向第二个block的起始地址,并且使这个地址内存放的是指针(*plink)。
注意:因为plink所指向的地址变了,此时pblk所指向的地址
内的内容由原来的**plink变为了*plink指针。
并且 *plink还未被赋值,则**plink值是未知的
5、pblk = pblk + blksize;
pblk不断的下移,以指向再下一个block的开始处。
pblk(new)= pblk(old)+blksize
当再次进行for循环时,重复上述过程,利用每个block首地址内的指针将每个Block链接起来组成空闲块链表。
同样,*plink被赋值后,指向pblk所指向的地址,则该地址的
内容为**plink,只不过我们并不需要取出**plink。
6、pmem->OSMemFreeList = addr; /*pmem->OSMemFreeList指向空闲块链表第一个block首地址
在完成for循环后,使pmem->OSMemFreeList指向addr,组成完整的空闲块链表。
总结:进行(void**)强制转换的目的其实就是为
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)