zoukankan      html  css  js  c++  java
  • 二维指针*(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**)强制转换的目的其实就是为了把所指向的地址的内容转换成一个指针。

    二、在OSMemGet( )函数内同样有一条强制转换为二维指针的指令:

     void      *pblk;

     执行操作:pmem->OSMemFreeList =*(void **)pblk;

    pblk被强制转换为二维指针,然后取出其内容*pblk,也就是pblk地址内存放的链接指针。

    意味着取出pblk的内容,由于pblk被强制转换成了二维指针,所以它的内容就不是一般的值,而是一个指针(这个指针指向下一个Block首地址)。

    三、在INT8U  OSMemPut (OS_MEM  *pmem, void *pblk) 函数内同样有类似的指令:

    ① *(void **)pblk  = pmem->OSMemFreeList;   // 将欲释放的块添加到空闲块链表最前面

    ② pmem->OSMemFreeList = pblk;

    首先要明白pmem->OSMemFreeList是指向空闲块链表第一个block的首地址的。

    语句①将pblk强制转换为二维指针后,再将pmem->OSMemFreeList赋值给pblk的内容(*pblk指针)。根据OSMemPut函数的定义,pblk是函数的形参,是欲释放的块的首地址。所以也就是将pmem->OSMemFreeList指针放入欲释放的块的首地址内,此处强制转换为二维指针的目的就是让欲释放的块的首地址内能存放指针。则这个块的首地址内的指针就是指向原先空闲块链表第一个Block的首地址的,也就是说这个块变成了空闲块链表第一个Block,实现了将释放的块添加到空闲块链表最前面的目的。

    然后语句②更新pmem->OSMemFreeList指针,使其指向新释放的块的首地址,这样就保证了pmem->OSMemFreeList始终指向空闲块链表第一个Block首地址。

  • 相关阅读:
    避免使用HttpClient的系统代理
    C#使用ILGenerator动态生成函数
    leveldb和fork的初始化顺序
    模拟阻尼运动
    [c++]printf的编译器静态检测
    Git Submodule管理项目子模块
    redis 读写分离主从服务类借鉴
    git pull 冲突
    git log 查看提交记录
    自带单例模式的redis类
  • 原文地址:https://www.cnblogs.com/locean/p/5421941.html
Copyright © 2011-2022 走看看