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首地址。

二维指针*(void **)的研究(uC/OS-II案例) 《转载》的更多相关文章

  1. C++实现离散余弦变换(参数为二维指针)

    C++实现离散余弦变换(参数为二维指针) 写在前面 到目前为止已经阅读了相当一部分的网格水印等方面的论文了,但是论文的实现进度还没有更上,这个月准备挑选一些较为经典的论文,将其中的算法实现.在实现论文 ...

  2. C语言里的指针探析——type *name[] 在函数参数里面,是一个二维指针

    type *name[] 在函数参数里面声明和不在函数里面声明其实不一样. type *name[] 如果在函数参数里声明,则name 是一个二维指针,并不是一个指针数组,而如果不在函数参数里声明,则 ...

  3. 通过数组初始化链表的两种方法:指向指针的引用node *&tail和指向指针的指针(二维指针)node **tail

    面试高频题:单链表的逆置操作/链表逆序相关文章 点击打开 void init_node(node *tail,char *init_array) 这样声明函数是不正确的,函数的原意是通过数组初始化链表 ...

  4. 最长公共子序列的C++实现---附二维指针的使用方法

    想了挺久到底第一篇在这儿的博客写什么好,刚好这两天又一次看到动态规划的LCS算法觉得还是有点意思的,就拿来写了写,第一篇博客就发它吧. #include<iostream> #includ ...

  5. 【视频开发】OpenCV中Mat,图像二维指针和CxImage类的转换

    在做图像处理中,常用的函数接口有OpenCV中的Mat图像类,有时候需要直接用二维指针开辟内存直接存储图像数据,有时候需要用到CxImage类存储图像.本文主要是总结下这三类存储方式之间的图像数据的转 ...

  6. uC/OS II原理分析及源码阅读(一)

    uC/OS II(Micro Control Operation System Two)是一个可以基于ROM运行的.可裁减的.抢占式.实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和 ...

  7. 【原创】uC/OS II 任务切换原理

    今天学习了uC/OS II的任务切换,知道要实现任务的切换,要将原先任务的寄存器压入任务堆栈,再将新任务中任务堆栈的寄存器内容弹出到CPU的寄存器,其中的CS.IP寄存器没有出栈和入栈指令,所以只能引 ...

  8. 【小梅哥SOPC学习笔记】NIOS II处理器运行UC/OS II

    SOPC开发流程之NIOS II 处理器运行 UC/OS II 这里以在芯航线FPGA学习套件的核心板上搭建 NIOS II 软核并运行 UCOS II操作系统为例介绍SOPC的开发流程. 第一步:建 ...

  9. 【C语言】二维指针做形参

    转自:http://hi.baidu.com/gpmzccqceabimqq/item/f499f057aa1520404eff208b 关键: 传入时强制类型转换 + 使用时自己手工寻址 今天写程序 ...

随机推荐

  1. Java实现SOAP协议 之 HelloWorld

    Java实现SOAP协议 之 HelloWorld SOAP是把基于HTTP的WEB技术与XML的灵活性和可扩展性组合在了一起. 准备: 1. 准备一个web server.Tomcat 下载地址 h ...

  2. SDL音频播放

    gcc -o tutorial03 tutorial03.c -lavutil -lavformat -lavcodec -lz -lm \`sdl-config --cflags --libs` A ...

  3. Java虚拟机内存优化实践

    前面一篇文章介绍了Java虚拟机的体系结构和内存模型,既然提到内存,就不得不说到内存泄露.众所周知,Java是从C++的基础上发展而来的,而C++程序的很大的一个问题就是内存泄露难以解决,尽管Java ...

  4. flume的自定义sink-Kafka

    1.创建一个agent,sink类型需指定为自定义sink        vi /usr/local/flume/conf/agent3.conf        agent3.sources=as1  ...

  5. asp.net 分页类

    PaginatedList.cs using System;using System.Collections.Generic;using System.Linq;using System.Web; n ...

  6. swift 创建tableView 并实现协议

    import UIKit class ViewController2: UIViewController,UITableViewDelegate,UITableViewDataSource{      ...

  7. C#winform检测电脑安装的.netframework版本和是否安装了某软件

    代码如下: //C#获取已安装 .NET Framework 版本 private static string[] GetDotNetVersions() { DirectoryInfo[] dire ...

  8. Linux系统调用system_call

    2016-03-25 张超的<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 我的虚拟环境和代码在http ...

  9. python 元类与定制元类

    1:元类 元类:类的创建与管理者 所有类的元类是type class a: pass print(type(a)) 结果:<class 'type'> 2:定制元类 类的实例化过程:(可看 ...

  10. NeralJS需求整理及思路

    NeralJs希望通过解析json数据,动态创建和渲染报表,每个报表呈现一定时间范围的数据,通过使用表格图表等不同的方式,使数据的呈现不死板,且更容易看出规律. 说通俗点,就是动态在一个区域内生成多张 ...