精华内容
下载资源
问答
  • 空闲磁盘存储空间的管理:建立磁盘空间管理模块。 建立相应的数据结构; 磁盘上建立一个文件文件长度设为10MB,用该文件来模拟一个磁盘,磁盘的物理块大小为512字节。 建立进程的数据结构; 时间的流逝可用下面几种...

    1. 课程设计任务、要求、目的

    1.1 课程设计任务

    空闲磁盘存储空间的管理:建立磁盘空间管理模块。
    建立相应的数据结构;
    磁盘上建立一个文件,文件长度设为10MB,用该文件来模拟一个磁盘,磁盘的物理块大小为512字节。
    建立进程的数据结构;
    时间的流逝可用下面几种方法模拟:(a)按键盘,每按一次可认为过一个时间单位;(b)响应WM_TIMER;
    使用两种方式产生进程对磁盘的请求:(a)自动产生,(b)手工输入
    将一批进程对磁盘的请求的情况存磁盘文件,以后可以读出并重放;
    显示每次磁盘的请求和空间释放后的相关数据结构的状态;
    显示每次磁盘的请求和空间释放后状态;
    支持的管理方法:空闲表法、空闲链表法、位示图法。

    1.2 课程设计目的和要求
    磁盘初始化时把磁盘存储空间分成许多块(扇区),这些空间可以被多个用户共享。用户作业在执行期间常常要在磁盘上建立文件或把已经建立在磁盘上的文件删去,这就涉及到磁盘存储空间的分配和回收。一个文件存放到磁盘上,可以组织成顺序文件(连续文件)、链接文件(串联文件)、索引文件等,因此,磁盘存储空间的分配有两种方式,一种是分配连续的存储空间,另一种是可以分配不连续的存储空间。怎样有效地管理磁盘存储空间是操作系统应解决的一个重要问题,通过这个课程设计可以使我们更好地熟悉掌握磁盘存储管理的原理和分配与回收算法,进一步掌握软件开发方法并提高解决实际问题的能力。

    2. 开发环境

    VS2017

    3. 相关原理及算法

    a) 进程等待队列与就绪队列
    进程因为一些执行条件不满足,会进入等待队列中进行等待。当条件满足时,将进入就绪队列中运行,由CPU决策运行。
    b) 空闲区表法
      空闲表法属于连续分配方式。它与内存管理中的动态分区分配方式雷同。将外存空间上一个连续未分配区域称为“空闲区”。操作系统为磁盘外存上所有空闲区建立一张空闲表,每个表项对应一个空闲区,空闲表包含“序号,第一空闲盘块号,空闲盘块数”等信息。它适用于连续文件结构。
    它为每个文件分配一个连续的存储空间。系统为外存上的所有空闲区建立一张空闲表,每个空闲区对应于一个空闲表项。
    c) 最佳适应算法(BestF比)
    该算法总是把既能满足要求,又是最小的空闲分区分配给作业。为了加速查找,该算法要求将所有的空闲区按其大小排序后,从递增顺序形成一个空自链。这样每次找到的第一个满足要求的空闲区,必然是最优的。孤立地看,该算法似乎是最优的,但事续上并不一定。因为每次分配后剩余的空间一定是最小的,在存储器中将留下许多难从利用的小空闲区。同时每次分配后必须重新排序,这也带来了一定的开销。
    特点:每次分配给文件的都是最合适该文件大小的分区。
    缺点:内存中留下许多难从剎利用的小的空闲区。
    d) 空闲链表法
       是将所有的空闲盘区拉成一条空闲链。根据构成链的基本元素的不同,可有两种链表方式:空闲盘块链、空闲盘区链。
      空闲盘块链
       它是将磁盘上的所有空闲存储空间,以盘块为基本元素拉成一条链。优点是用于分配和回收一个盘块的过程非常简单;缺点是空闲盘块链可能很长。
      空闲盘区链
    这是将磁盘上的所有空闲盘区(每个盘区可包含若干个盘块)拉成一条链。在每个盘区上除了含有用于指示下一个空闲盘区的指针外,还应标有指明本盘区大小(盘块数)的信息。这方法分配和回收过程较复杂,但空闲盘区链较短。
    e) 显式链接
    使用文件分配表(File Allocation Table,FAT)。每个磁盘分区的开始部分用于存储FAT表。磁盘上的每个块都在该表中登记,并占用一个表项,FAT表可以通过块的编号来索引,FAT的每个表项含有文件的下一块的块号,这样FAT就可以像链表一样使用。系统首先根据目录文件中的第一块的块号去检索FAT表,从中得到文件的下一个盘块号,以此类推,直到该文件的最后一块,该块对应FAT表的值为文件结束标志。在FAT表中,未使用的块用0表示,因此,当一个文件需要分配新的存储空间时,就在FAT表中查找第一个标志为0的块,用新分配块的块号替换该条目的值,并把该块链接到文件的尾部。

    f) 位示图法
      这种方法是在外存上建立一张位示图(bitmap),记录文件存储器的使用情况。每一位仅对应文件存储器上的一个物理块,取值0和1分别表示空闲和占用。文件存储器上的物理块依次编号为:0、1、2、…。
      位示图是利用二进制的一位来表示磁盘中一个盘块的使用情况。当其值为“0”时,表示对应的盘块空闲;为“1”时表示已分配。由所有盘块对应的位构成一个集合,称为位示图。位示图也可描述为一个二维数组map:Var map:array[1…m,1…n]of bit;
      盘块的分配
      根据位示图进行盘块分配时,可分三步进行:
      ·顺序扫描位示图,从中找出一个或一组值均为“0”的二进制位;
      ·将找到的二进制位,转换成与之相应的盘块号;
      ·修改位示图,令map[i,j]=1。
      盘块的回收
      盘块的回收分两步:
      ·将回收盘块的盘块号转换成位于图中的行号和列号。转换公式为:
      i=(b-1)DIVn+1
      j=(b-1)MODn+1
    ·修改位示图。
    令map[i,j]=0。

    4. 系统结构和主要的算法设计思路

    该系统用于模拟进程所占用磁盘块的管理。分为四部分,分别是进程与时间流的模拟、空闲盘块法、空闲链表法、位示图法。
    其运行流程图如图1所示。
    在这里插入图片描述 图1
    在这里插入图片描述

    图2
    空闲盘块表最佳适应算法 申请块
    在这里插入图片描述

    图3
    空闲盘块最佳适应算法 归还块

    空闲链表法的设计思想:
    首先使用一个显示链表,用int型数组来表示这个链表,其长度为20480,也就是总快数,即每一块都有一个对应的位置。
    申请物理块时,进程记录空闲链表开头的物理块位置,并依照请求的物理块数,在显示链表中寻找尚未分配的物理块(即对应位置标记为0的物理块),将对应位置的标记置为下一物理块所在位置,将最后一个位置的标记置为-1即可。
    归还物理块时,进程根据其记录的开头位置,顺着显示链表直到结尾(即为-1的位置),并将经历的位置标记均置为0。
    位示图法的设计思想:
    该位示图使用了一维int型数组,其长度为640,其中每一个int型数据占据32位,每一位都代表了一个物理块的占用情况,位为1表示被占用,为0表示空闲。
    申请物理块时,按照从低位到高位的顺序将int数组置为1,假设申请的位置为第s块,那么其对应int数组的位置为第(s/32)个int数,第(s%32)位,只需要将其置为1即可。注意,这个过程中,进程需要记录下请求的每一个物理块的位置,便于接下来的归还物理块。
    归还物理块时,按照进程记录的物理块位置,将对应位置的int数位置为0,具体计算规则是:假设申请的位置为第s块,那么其对应int数组的位置为第(s/32)个int数,第(s%32)位,只需要将其置为0即可。最后释放进程用以记录位置的空间。

    5. 程序实现—主要数据结构

    进程的数据结构

    struct Process {
    	int PID;//进程ID
    	char *name;//进程名
    	int size;//进程大小
    	int BlockNum;//进程所占磁盘块数
    	int state;//进程的7种状态,1~7分别对应:创建、就绪、执行、阻塞、挂起阻塞、挂起就绪、终止
    	int Rtime;//间隔时间(到达时间)
    	int time;//运行时间
    	struct Process*next;//下一个进程(这里使用了进程的链表)
    	int start;//记录进程所占据磁盘号的开始位置
    	int *Block;//位示图时,记录的位置指针
    }*ReadyQueue,*RunningQueue;//就绪队列和执行队列
    

    说明:此处的Rtime是为了模拟进程依次经过随机时间到达,是为了模拟时间间隔。
    空闲盘块表的数据结构

    struct FreeTableItem {
    	int ID;//空闲块ID
    	int first;//空闲块开始的第一块
    	int BlockNum;//空闲块块数
    	int state;//空闲块状态
    }FreeTable[MAXLENGTH];//申请空闲盘快表,其最大长度为1024
    

    空闲链表的数据结构

    int PAT[BLOCKNUMBER];//显式链表的存储方法
    

    位示图的数据结构

    int BitMap[BLOCKNUMBER / 32];//int占据32位,此位示图共row=640行
    

    6. 程序实现—主要程序清单

    空闲表法核心函数:
    ApplyBlock1()表示申请磁盘块,ReturnBlock1表示归还磁盘块

    int ApplyBlock1(struct Process **aProcess)
    /*返回值为1表示申请成功,返回值为0表示申请失败*/
    {
    	for (int i = 0; i < TableItemNum; i++)
    	{
    		if ((*aProcess)->BlockNum<= FreeTable[i].BlockNum)
    		{
    			(*aProcess)->start = FreeTable[i].first;//进程记录起始位置
    			FreeTable[i].first = FreeTable[i].first + (*aProcess)->BlockNum;
    			FreeTable[i].BlockNum = FreeTable[i].BlockNum - (*aProcess)->BlockNum;
    			if (FreeTable[i].BlockNum == 0)//如果减去后等于0,则从空闲块表中删除这块
    			{
    				DeleteFreeItem(i);
    			}
    			LeftMoveFreeItem(i);//申请之后可能变小了,此时需要左移
    			return 1;
    		}
    	}
    	return 0;
    }
    
    int ReturnBlock1(struct Process aProcess)
    {
    	for (int i = 0; i < TableItemNum; i++)//如果可以向上或者向下合并
    	{
    		if (aProcess.start == FreeTable[i].first + FreeTable[i].BlockNum)//如果可以向上合并
    		{
    			FreeTable[i].BlockNum += aProcess.BlockNum;
    			//向上合并之后,再找找有没有可以向下合并的
    			for (int j = 0; j < TableItemNum; j++)
    			{
    				if (FreeTable[i].first + FreeTable[i].BlockNum == FreeTable[j].first)
    				{
    					FreeTable[i].BlockNum += FreeTable[j].BlockNum;
    					DeleteFreeItem(j);//向下合并之后,删除下面那个表项
    					break;
    				}
    			}
    			RightMoveFreeItem(i);//后移第i块,使空闲表由小到大排列
    			return 1;//合并成功
    		}
    		if (aProcess.start + aProcess.BlockNum == FreeTable[i].first)//如果可以向下合并
    		{
    			FreeTable[i].BlockNum += aProcess.BlockNum;
    			FreeTable[i].first = aProcess.start;
    			//向下合并之后,再找找有没有可以向上合并的
    			for (int j = 0; j < TableItemNum; j++)
    			{
    				if (FreeTable[i].first == FreeTable[j].first+FreeTable[j].BlockNum)
    				{
    					FreeTable[j].BlockNum += FreeTable[i].BlockNum;
    					DeleteFreeItem(i);//向上合并之后,删除下面那个表项
    					break;
    				}
    			}
    			RightMoveFreeItem(i);//后移第i块,使空闲表由小到大排列
    			return 1;//合并成功
    		}
    	}
    	//既不能向上,也不能向下合并,此时需要添加一个新表项
    	int ID;
    	for (int i = 0; i < TableItemNum; i++)
    	{
    		ID = newFreeTableID();
    		if (aProcess.BlockNum < FreeTable[i].BlockNum)//因为空闲表内部已经由小到大排列,所以可以直接从头到尾,向后寻找一块合适的位置
    		{
    			if (TableItemNum >= MAXLENGTH) return 0;//超过了表最大可容纳项目数,此时就算归还了物理块,也会导致无法记录,因此退回
    			for (int j = TableItemNum; j > i; j--)//第i块开始整体后移
    			{
    				FreeTable[j] = FreeTable[j - 1];
    			}
    
    			//写入新的表项
    			FreeTable[i].ID = ID;
    			FreeTable[i].BlockNum = aProcess.BlockNum;
    			FreeTable[i].first = aProcess.start;
    			FreeTable[i].state = 1;
    			TableItemNum++;
    			aProcess.state = 7;//进入停止运行状态
    			return 1;
    		}
    		if (i == TableItemNum)//运行到最后还是没发现比当前表项大的表项,所以这一表项插入最后
    		{
    			//写入新的表项
    			FreeTable[i].ID = newFreeTableID();
    			FreeTable[i].BlockNum = aProcess.BlockNum;
    			FreeTable[i].first = aProcess.start;
    			FreeTable[i].state = 1;
    			TableItemNum++;
    			aProcess.state = 7;//进入停止运行状态
    			return 1;
    		}
    	}
    	return 0;//特殊情况,例如表没有初始化
    }
    

    空闲链表法

    int ApplyBlock2(struct Process **aProcess)
    {
    	if ((*aProcess)->BlockNum > surplus2)//超过剩余块数,分配失败
    		return 0;
    	int i = 0;
    	for (; i < BLOCKNUMBER; i++)
    	{
    		if (PAT[i] == 0)
    			break;
    	}
    	int Head = i;//找到了可以用的头部
    	(*aProcess)->start = Head;//进程的开始位置,指向头部
    	int BlockNum = (*aProcess)->BlockNum;//需要占用的磁盘块数
    	int Num = 1;//已经占用的磁盘块数
    	for (int j = i+1; j < BLOCKNUMBER; j++)
    	{
    		if (PAT[j] == 0)
    		{
    			Num++;
    			PAT[i] = j;
    			i = j;
    		}
    		if (Num == BlockNum)//如果达到了要求的数目,则将其下一个指向的空闲位置变成-1,表示链表结束
    		{
    			PAT[i] = -1;
    			surplus2 -= BlockNum;//剩余块数减少
    			return 1;
    		}
    	}
    	return 0;
    }
    int ReturnBlock2(struct Process aProcess)
    {
    	int now = aProcess.start;
    	int pre = now;//上一个位置
    	now = PAT[now];//当前位置
    	while (PAT[now] != -1)
    	{
    		PAT[pre] = 0;
    		pre = now;
    		now = PAT[now];
    	}
    	PAT[pre] = 0;//最后一个非-1的数归零
    	PAT[now] = 0;//最后一个-1也归零
    	//全部归0之后
    	surplus2 += aProcess.BlockNum;
    	return 1;//释放成功
    }
    

    位示图法

    int ApplyBlock3(struct Process **aProcess)
    {
    	if ((*aProcess)->BlockNum > surplus3) 
    		return 0;//超过剩余块数,无法分配
    	int BlockNum = (*aProcess)->BlockNum;//待分配的块数
    	if((*aProcess)->Block==NULL)
    		(*aProcess)->Block = (int *)malloc(sizeof(int)*BlockNum);//为进程申请一段内存,用以存储每一块的位置信息
    	int index = 0;
    	for (int i = 0; i < row; i++)
    	{
    		for (int j = 0; j < 32; j++)
    		{
    			if (Numis(BitMap[i], j) == 0)//找到为0的位置
    			{
    				SetBit(BitMap[i], j);//将其置为1
    				((*aProcess)->Block)[index] = i * 32 + j;//位置记录下来
    				++index;//索引后推
    				--BlockNum;//剩余待分配块数-1
    				--surplus3;//剩余可分配块数-1
    				if (BlockNum <= 0)//分配结束了
    					return 1;
    			}
    		}
    	}
    	return 0;
    }
    int ReturnBlock3(struct Process aProcess)
    {
    	int *temp = aProcess.Block;
    	int a;
    	for (int i = 0; i < aProcess.BlockNum; i++)
    	{
    		a = temp[i];
    		ClearBit(BitMap[a / 32], a % 32);//将对应位置置为0
    	}
    	surplus3 += aProcess.BlockNum;//剩余块数增加
    	free(aProcess.Block);//释放这块空间,这之后,即使将指针置空也没用,因为这函数的操作数据是复制来的,这里的指针会变成野指针,所以需要自己手动置空
    	return 1;
    }
    

    7. 程序运行的主要界面和结果截图

    在这里插入图片描述

    在这里插入图片描述

    空闲表法

    在这里插入图片描述

    如屏幕中所显示,一行有4个项目,这是为了在空闲表中项目数较大的情况下,便于观察。因为此处采用的是首次适应算法,队列中表项按照由小到大的顺序排列。例如图中前5项显示为12、16、18、25、39.
    在这里插入图片描述

    100个随机进程运行结束时,空闲表回归到只剩下1块的情况,磁盘成功分配,又成功回收。
    运行结束后,按任意键返回主菜单
    在这里插入图片描述

    此时如果有存储,会提醒上次存储的进程,可以直接读取
    在这里插入图片描述

    如上,便读取了100个进程,第二次我们使用位示图法来模拟
    位示图法

    在这里插入图片描述
    在这里插入图片描述

    通过位示图,我们能看到许多数被填充为-1,这是int型数据,即-1代表了32位全1,位示图正常运行。
    在这里插入图片描述

    运行到后期,通过观察位示图可以发现,当前磁盘空间几乎被填满。紧接着,磁盘块开始归还,如下图。
    在这里插入图片描述

    最终磁盘块完全归还,位示图变为全0
    在这里插入图片描述

    空闲链表法
    因为显式链表有20480位,故太大不方便展示,这里仅展示前1000位。
    在这里插入图片描述

    由图可知,正常分配。

    688个时间单位后,显式链表变为全0,分配结束。
    在这里插入图片描述

    手动创建进程的示例:

    在这里插入图片描述

    8. 总结和感想体会

    这次课程设计,让我充分回顾了操作系统中关于物理块分配的内容,加深了理解,通过一系列的努力,终于完成。这其中还涉及到了windows的定时机制,让我大开眼界。使用c语言来编写代码,给人的感觉是很麻烦,无论是申请还是释放内存都要格外注意,否则很可能导致无法存储或者内存泄露。而完成了这样一个系统,用到了链表和顺序表,也复习了数据结构,这样一次动手实践,让我的动手能力很好的得到了提升。

    完整代码请于此处下载:https://download.csdn.net/download/qq_42361182/12787808

    展开全文
  • 本实验采用动态申请空间方法存储数据  int nUsedNum = 0;  if (argc != 2)  {<!-- -->  Print_Help();  return -1;  }  else {<!-- -->  //为...
  • c语言数据结构

    2012-03-25 21:38:21
    1、4、4 算法的存储空间需求 2 线性表 2、1 线性表的类型定义 2、2 线性表的顺序表示和实现 实验一 2、3 线性表的链式表示和实现 2、3、1 线性链表 2、3、2 循环链表 实验二 2、3、3 双向链表 2、4 一元多项式的...
  • 数据结构 c语言

    2012-12-30 19:26:36
    第8章 动态存储管理 8.1 概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.3.1 可利用空间表的结构 8.3.2 分配算法 8.3.3 回收算法 8.4 伙伴系统 8.4.1 可利用空间表的结构 8.4.2 分配算法 8.4.3 回收...
  • C语言深度揭秘

    2012-04-12 13:21:16
    1.11.2,节省空间,避免不必要的内存分配,同时提高效率.................................... 35 1.12,最易变的关键字----volatile................................ 36 1.13,最会带帽子的关键字----extern.........
  • c语言深度剖析

    2012-08-18 18:59:34
    c语言 深度 剖析 解开程序员面试笔试的秘密 第一章关键字..................................................................................................................................9 1.1,最宽恒...
  • C语言深度解剖(c语言进阶的好教程) 目 录 第一章 关键字................................................................................................................................... 9 1.1,最宽...
  • C语言深度解剖

    2012-08-09 19:43:35
    1.11.2,节省空间,避免不必要的内存分配,同时提高效率.................................... 35 1.12,最易变的关键字----volatile..............................................................................
  • 第8章 动态存储管理 8.1 概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.3.1 可利用空间表的结构 8.3.2 分配算法 8.3.3 回收算法 8.4 伙伴系统 8.4.1 可利用空间表的结构 8.4.2 分配算法 8.4.3 回收算法 8.5 ...
  • C语言深度解剖>>解开程序员面试笔试的秘密》由作者结合自身多年嵌入式C语言开发经验和平时讲解C语言的心得体会整理而成,其中有很多作者独特的见解或看法。由于并不是从头到尾讲解C语言的基础知识,所以本书并不...
  • 《数据结构》(C语言版)严蔚敏

    热门讨论 2012-08-31 09:29:24
    第8章 动态存储管理 8.1 概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.3.1 可利用空间表的结构 8.3.2 分配算法 8.3.3 回收算法 8.4 伙伴系统 8.4.1 可利用空间表的结构 8.4.2 分配算法 8.4.3 回收算法 8.5 ...
  • (5)源程序清单和执行结果:清单中应有足够的注释 题目:手机通信录管理系统设计 需求分析 手机通信录采用文件存储,因而要提供文件的输入输出操作;查看功能要提供显示操作;增加新数据的过程要检查是否有重复,...
  • 数据结构(C语言版)

    2012-08-05 03:49:56
    第8章 动态存储管理 8.1 概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.3.1 可利用空间表的结构 8.3.2 分配算法 8.3.3 回收算法 8.4 伙伴系统 8.4.1 可利用空间表的结构 8.4.2 分配算法 8.4.3 回收算法 8.5 ...
  • 严蔚敏:数据结构题集(C语言版)

    热门讨论 2009-09-02 18:38:34
    1.4.4 算法的存储空间需求 第2章 线性表 2.1 线性表的类型定义 2.2 线性表的顺序表示和实现 2.3 线性表的链式表示和实现 2.3.1 线性链表 2.3.2 循环链表 2.3.3 双向链表 2.4 一元多项式的表示及相加 第3章 栈和...
  •  用于文件目录和路径操作。 (4)诊断函数  用于内部错误检测。 (5)图形函数  用于屏幕管理和各种图形功能。 (6)输入输出函数  用于完成输入输出功能。 (7)接口函数  用于与DOS,BIOS和硬件的接口。 (8)字符串...
  • (2) 数据的逻辑结构在计算机存储空间中的存放形式称为数据的______。 答:模式#逻辑模式#概念模式 (3) 若按功能划分,软件测试的方法通常分为白盒测试方法和______测试方法。 答:黑盒 (4) 如果一个工人可管理多个...
  • 算法程序所占的存储空间 D. 算法执行过程中所需要的存储空间 (13) 设一棵完全二叉树共有699个结点,则在该二叉树中的叶子结点数为(B) 注:利用公式n=n0+n1+n2、n0=n2+1和完全二叉数的特点可求出 A. 349 B. 350 C. ...
  • C语言高级编程与实例剖析》源码

    热门讨论 2010-09-24 11:39:21
    C语言高级编程与实例剖析》随书源码 第1章 内存管理 1. 1.1 预备知识 1 1.1.1 PC存储器结构 1 1.1.2 CPU地址线宽度 3 1.1.3 内存管理方式 5 1.1.4 内存编译模式 6 1.1.5 堆概念和结构 9 ...
  •  16.9 文件管理函数 307  16.9.1 删除文件 307  16.9.2 给文件重命名 308  16.9.3 复制文件 309  16.10 使用临时文件 311  16.11 总结 312  16.12 问与答 312  16.13 作业 313  16.13.1 小测验 ...
  • 1.11.2,节省空间,避免不必要的内存分配,同时提高效率.................................... 35 1.12,最易变的关键字----volatile...............................................................................
  • 第8章 动态存储管理 8.1 概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.3.1 可利用空间表的结构 8.3.2 分配算法 8.3.3 回收算法 8.4 伙伴系统 8.4.1 可利用空间表的结构 8.4.2 分配算法 8.4.3 回收算法 8.5 ...
  • C语言深度解剖_word版

    2010-04-25 22:21:15
    C语言深度解剖 word版 解开程序员面试笔试的秘密第一章关键字......................................................................................................................9 1.1,最宽恒大量的关键字...
  • 3.5.4 字符数据在内存中的存储形式及使用方法 41 3.5.5 字符串常量 41 3.5.6 符号常量 42 3.6 变量赋初值 42 3.7 各类数值型数据之间的混合运算 43 3.8 算术运算符和算术表达式 44 3.8.1 C运算符简介 44 3.8.2 算术...
  • 3.5.4 字符数据在内存中的存储形式及使用方法 41 3.5.5 字符串常量 41 3.5.6 符号常量 42 3.6 变量赋初值 42 3.7 各类数值型数据之间的混合运算 43 3.8 算术运算符和算术表达式 44 3.8.1 C运算符简介 44 3.8.2 算术...
  • 概念解析存储存储类就是存储类型,描述C语言变量在什么地方存储,内存中有多重管理方法,堆,栈,数据段,bss段,text段,变量存储类属性描述的就是这个变量存储于什么内存段中。作用域作用域就是该变量起作用的...

    概念解析

    存储类

    存储类就是存储类型,描述C语言变量在什么地方存储,内存中有多重管理方法,堆,栈,数据段,bss段,text段,变量存储类属性描述的就是这个变量存储于什么内存段中。

    作用域

    作用域就是该变量起作用的范围,C语言变量起作用的范围是当前代码块。

    生命周期

    生命周期描述的是运行时给变量分配内存空间,以及回收该变量的内存空间的一个阶段。

    链接属性

    把源代码编译成目标文件之后,目标文件总包含众多的符号和各种段,运行时函数名,变量名需要和内存对应起来,这要靠符号做链接,将目标文件链接生成最终的可执行程序的时候,就是把符号和对应的段链接起来了,C语言中有三种链接属性:外链接,内链接和无连接。

    内存映像

    代码段,只读数据段

    代码段在linux中又叫做文本段,名称为.text,只读数据段存储的是在程序运行期间只能读不能写的数据

    数据段,bss段

    数据段主要存储,显示初始化为非0的全局变量,显式初始化为非0的静态局部变量。bss段主要存储,显示或者默认初始化为0的全局变量,

    堆内存

    堆内存需要程序员自己手动操作,用的时候申请,使用完毕之后需要手动释放。

    文件映射去

    文件映射区就是进程打开文件之后,将文件的内容从硬盘读取到这个文件映射区中,以后直接在该映射区中操作该文件,操作完毕之后,再将映射区中的内容写入到硬盘的文件中。

    局部变量会被分配到栈上,函数调用传参过程中,也会使用到栈。

    内核映射区

    将操作系统内核程序映射到这个区域,linux中的每个进程,都以为整个系统只有程序自身和内核而已,进程认为内存0xc0000000以下都是自身的活动空间,以上的是操作系统内核的空间,使用虚拟地址映射,每个进程都在自己独立的内存空间中,都认为自身都掌握着0-3G这段内存空间,但是,所有的进程看到的内核都是同一个内核。

    OS程序和裸机程序的差异

    C语言的程序运行时对环境有一定的要求,单独写的C语言程序无法直接在内存中运行,需要通过外部的协助,这些协助的代码称为构建C语言运行时环境的代码。

    OS程序

    在操作系统下这些代码已经写好并且会自动我们写的C语言程序中,主要作用是:赋值全局变量,清理bss段等。这些操作在我们的程序启动时都会自动执行。

    裸机程序

    在裸机程序中,没人帮我们加载这一段环境构建代码,需要我们手动自己去做。

    存储类

    auto

    auto在C语言中只能修饰局部变量,被auto修饰的局部变量,会被分配到栈上,auto关键字可以省略,我们平时定义的局部变量自动就是auto修饰的,只不过我们在写法上省略了而已。

    static

    static在C语言中有两种用法,这两种用法彼此之间没有关系,是独立的,第一种用法是用于修饰局部变量,形成静态局部变量,第二种用于修饰全局变量,形成静态全局变量,这两种含义是完全不同的,静态全局变量和非静态全局变量区别在于链接属性不同。

    static修饰局部变量时,存储类发生了变化,非静态局部变量分配在栈上,但是静态局部变量根据初始化值分配在了数据段或者bss段,这些都和全局变量非常类似,区别在于作用域和链接属性不同,静态局部变量作用域是当前代码块,链接属性是无连接,全局变量作用域是当前文件,链接属性为外连接。

    register

    register修饰的变量,编译器会尽量将其分配在寄存器中,可以提高读写效率,常用于变量会被反复高频率使用的场景,可以极大提升程序的运行效率。编译器只是承诺尽量会被分配在寄存器中,但是不能保证。

    extern

    extern主要用于声明全局变量,这种变量可以在另外一个源文件中被访问到,C语言中程序的编译是以源文件为单位的,每个文件在编译时根本不会考虑其他文件中的内容。其他文件中要使用extern变量时,需要先声明,才能使用。

    volatile

    表示一个变量可以在编译器之外改变,编译器在编译当前代码时无法预知的改变,就叫做编译器之外的改变,编译器在遇到volatile修饰的变量时,就不会对该变量的访问进行优化,就不会出现错误。

    restrict

    在C99中加入的关键字,只用于限定指针以及指针访问,该关键字告知编译器,所有修改该指针所指向内容的操作,只能通过该指针进行,不存在其他途径,以帮助编译器进行代码优化,生成更有效率的汇编代码。

    作用域

    作用域表示变量在什么范围内起作用。

    局部变量的作用域

    局部变量的作用域是在代码块当中,代码块就是一对”{}”括起来的部分,仅限于定义这个局部变量的代码块之后的部分。

    函数和全局变量的作用域

    函数和全局变量的作用域,是整个源代码文件,具体说是在整个源码文件定义了函数或者全局变量之后的范围,因为定义之前由于缺少声明所以没法使用

    同名变量的掩蔽规则

    编程的时候不可避免会出现同名变量,变量同名后不一定会出错,如果两个变量的作用域不同且没有交叠,则没有任何影响,如果作用域有交叠,则C语言规定,在交叠范围内,作用域小的变量会掩蔽作用域大的变量。

    生命周期

    掌握变量的生命周期有助于理解变量的行为特征。

    栈变量的生命周期

    存储在栈上的都是局部变量,生命周期是临时的,在代码执行过程中按照需要创建,使用和消亡。

    堆变量的生命周期

    堆内存空间是客观存在的,由操作系统维护,由程序去申请,使用完毕之后释放。堆变量从申请时诞生,使用,直到调用free的时候消亡,所以说堆内存的生命周期就是在mallo和free之间。

    数据段,bss段的生命周期

    全局变量的生命周期是永久的,在程序执行时诞生,在程序结束时消亡。全局变量所占用的内存不能被程序自动释放,所以说如果程序申请了过多的全局变量,会导致程序占用了大量的内存空间。

    代码段,只读段的生命周期

    代码段和只读段的生命周期和全局变量一样是永久的,代码段,只读段中存储的是函数,还可能有const类型的常量和字符串常量,这些的生命周期一般我们不会过多关心。

    链接属性

    C语言程序是由多个源文件和多个头文件组成的,程序的生成过程就是将这些文件编译和链接起来。编译是为了将函数,变量等转化为.o机器码,链接是为了将各个独立的机器码函数链接起来形成一个整体的二进制可执行程序。

    编译器工作时,以文件为单位依次读入文件,链接的时候,把编译生成的.o文件输入,链接生成一个可执行程序。C语言中有三种链接属性:

    • 外链接,可以在整个程序范围内进行连接,可以在整个程序的范围内找到,并进行连接,例如函数和全局变量,可以跨文件查找和链接
    • 内链接,指的是可以在目标文件内部进行链接和查找,不能在文件外部查找连接,例如static修饰的函数或者全局变量
    • 无链接,表示该符号不参与连接,例如局部变量

    函数和全局变量同名冲突

    由于函数和全局变量都属于外链接属性,因此在一个程序的所有源文件中,不能出现同名的全局变量或者函数,为了解决这种冲突,我们可以把明确不会被其他文件引用的函数或者全局变量使用static修饰为内链接属性,这样就不会被外部链接到了。

    展开全文
  • 1.11.2,节省空间,避免不必要的内存分配,同时提高效率.................................... 35 1.12,最易变的关键字----volatile...............................................................................

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 197
精华内容 78
关键字:

文件存储空间管理方法c语言

c语言 订阅