动态内存分配
c++内存分配机制

C++的内存分配机制可以分为四个区域:堆区、栈区、全局/静态存储区和常量存储区。
1. 堆区:动态内存分配区,程序在运行时可以向该区域申请一定大小的内存,用malloc或new来申请,用free或delete来释放。
2. 栈区:存放函数的参数值和局部变量,由编译器自动分配和释放,其操作方式类似于数据结构中的栈。
3. 全局/静态存储区:全局变量和静态变量被存放在此区域中,包括初始化的全局变量和静态变量(空白初始化的全局变量和静态变量也会被存放在此区域),全局变量和静态变量在程序整个运行期间一直被保留。
4. 常量存储区:常量被存放在此区域中,不允许修改。
C++内存分配机制遵循二八定律,即80%的内存空间被80%的程序所使用,而剩下的20%的内存空间则被浪费。
因此,在编写C++程序时,应该尽可能地利用好内存空间,避免内存空间的浪费。
c语言动态分配的用法

c语言动态分配的用法C语言中,动态内存分配是通过使用malloc、calloc和realloc等函数来实现的。
动态分配内存可以根据程序运行时的需要来动态分配和释放内存空间,提高程序的灵活性和效率。
1. malloc函数:用于在堆(heap)中分配指定大小的内存空间。
其函数原型为void* malloc(size_t size),其中size为要分配的内存空间的大小(以字节为单位)。
例如,以下代码动态分配了一个包含5个整数的整型数组的内存空间,并将其地址赋给指针变量p:```cint* p = (int*)malloc(5 * sizeof(int));```2. calloc函数:用于在堆中分配指定数量和大小的连续内存空间,并将其初始化为零值。
其函数原型为void* calloc(size_t num,size_t size),其中num为要分配的元素个数,size为每个元素的大小。
例如,以下代码动态分配了一个包含5个整数的整型数组的内存空间,并将其地址赋给指针变量p:```cint* p = (int*)calloc(5, sizeof(int));```3. realloc函数:用于重新分配已分配内存空间的大小。
其函数原型为void* realloc(void* ptr, size_t size),其中ptr为指向已分配内存空间的指针,size为重新分配的内存空间的大小。
例如,以下代码将已分配内存空间的大小重新设置为10个整数,并将其地址赋给指针变量p:```cint* p = (int*)malloc(5 * sizeof(int));int* q = (int*)realloc(p, 10 * sizeof(int));if (q != NULL) {p = q;}```4. free函数:用于释放由malloc、calloc和realloc函数分配的内存空间。
其函数原型为void free(void* ptr),其中ptr为指向要释放的内存空间的指针。
c语言malloc函数用法

c语言malloc函数用法引言:c语言malloc函数是C语言中应用最为普遍的一种动态内存分配方法,它可以提供大量内存来存储一个数组或者指针数组,当用完这些内存后又可以释放出来,这使得C语言有一定的灵活性,在C语言中使用动态内存分配和管理的重要性不言而喻。
一、malloc函数的定义malloc函数(memory allocation,动态内存分配)是由C语言提供的函数,它的主要作用是从堆中提供指定数量的连续字节以供调用者使用,一定要注意,每次调用malloc函数必须指定分配内存大小,这个大小是以字节为单位的,malloc函数的原型如下:void *malloc(unsigned int size);这里的size表示申请动态内存的大小,以字节为单位,malloc 函数的返回值是void*,这是个指针,指向分配的内存的首地址,如果申请失败,则返回空指针。
二、malloc函数的使用1、分配单个变量最常见的malloc函数是用来分配单个变量,比如申请一个int 型变量,则要申请4个字节的内存,这个时候只需要调用malloc函数:int *p;p = (int *)malloc(sizeof(int));2、分配动态数组C语言中很多时候要申请动态数组,比如申请长度为10的int型数组,则需要申请40个字节的内存,只需要将malloc函数的参数改为10*sizeof(int)即可:int *p;p = (int *)malloc(10*sizeof(int));三、malloc函数的缺点1、效率低malloc函数的效率比较低,每次申请内存都要从堆中查找,为了满足连续内存的要求,可能要将内存进行移动,这会导致效率比较低。
2、不能做复杂的内存管理malloc默认情况下只能用来分配和释放内存,不能对内存空间进行任何复杂的操作,例如,无法根据需要调整内存大小,无法释放内存中的某一部分,也无法把多个内存块合并为一个块等。
操作系统-动态分区分配算法实验报告

实验题目:存储器内存分配设计思路:1.既然是要对内存进行操作,首先对和内存相关的内容进行设置我使用的是用自定义的数据结构struct来存放内存中一个内存块的内容包括:始地址、大小、状态(f:空闲u:使用e:结束)之后采用数组来存放自定义的数据类型,这样前期的准备工作就完成了2.有了要加工的数据,接下来定义并实现了存放自定义数据类型的数组的初始化函数和显示函数,需要显示的是每个内存块的块号、始地址、大小、状态3.接着依此定义三种动态分区分配算法首次适应算法、最佳适应算法和最差适应算法4.对定义的三种算法逐一进行实现①首次适应算法:通过遍历存放自定义数据类型的数组,找到遍历过程中第一个满足分配大小的内存块块号i,找到之后停止对数组的遍历,将i之后的块号逐个向后移动一个,然后将满足分配大小的内存块i分为两块,分别是第i块和第i+1块,将两块的始地址、大小、状态分别更新,这样便实现了首次适应算法②最佳适应算法:和首次适应算法一样,首先遍历存放自定义数据类型的数组,找到满足分配大小的内存块后,对内存块的大小进行缓存,因为最佳适应是要找到最接近要分配内存块大小的块,所以需要遍历整个数组,进而找到满足分配大小要求的而且碎片最小的块i,之后的操作和首次遍历算法相同③最差适应算法:和最佳适应算法一样,区别在于,最佳适应是找到最接近要分配内存块大小的块,而最差适应是要找到在数组中,内存最大的块i,找到之后的操作和最佳适应算法相同,因此不在这里赘述。
5.定义并实现释放内存的函数通过块号找到要释放的内存块,把要释放的内存块状态设置成为空闲,查看要释放的块的左右两侧块的状态是否为空闲,如果有空闲,则将空闲的块和要释放的块进行合并(通过改变块的始地址、大小、状态的方式)6.定义主函数,用switch来区分用户需要的操作,分别是:①首次适应②最佳适应③最差适应④释放内存⑤显示内存⑥退出系统实验源程序加注释:#include<bits/stdc++.h>#define MI_SIZE 100 //内存大小100typedef struct MemoryInfomation//一个内存块{int start; //始地址int Size; //大小char status; //状态 f:空闲 u:使用 e:结束} MI;MI MList[MI_SIZE];void InitMList() //初始化{int i;MI temp = { 0,0,'e' };for (i = 0; i < MI_SIZE; i++){MList[i] = temp;}MList[0].start = 0; //起始为0MList[0].Size = MI_SIZE;//大小起始最大MList[0].status = 'f'; //状态起始空闲}void Display() //显示{int i, used = 0;printf("\n---------------------------------------------------\n");printf("%5s%15s%15s%15s", "块号", "始地址", "大小", "状态");printf("\n---------------------------------------------------\n");for (i = 0; i < MI_SIZE && MList[i].status != 'e'; i++){if (MList[i].status == 'u'){used += MList[i].Size;}printf("%5d%15d%15d%15s\n", i, MList[i].start, MList[i].Size, MList[i].status == 'u' ? "使用" : "空闲");}printf("\n----------------------------------------------\n");}void FirstFit(){int i, j, flag = 0;int request;printf("最先适应算法:请问你要分配多大的内存\n");scanf("%d", &request);for (i = 0; i < MI_SIZE && MList[i].status != 'e'; i++){if (MList[i].Size >= request && MList[i].status == 'f') {if (MList[i].Size - request <= 0){MList[i].status = 'u';}else{for (j = MI_SIZE - 2; j > i; j--){MList[j + 1] = MList[j];}MList[i + 1].start = MList[i].start + request; MList[i + 1].Size = MList[i].Size - request;MList[i + 1].status = 'f';MList[i].Size = request;MList[i].status = 'u';flag = 1;}break;}}if (flag != 1 || i == MI_SIZE || MList[i].status == 'e'){printf("没有足够大小的空间分配\n");}Display();}void BadFit(){int i, j = 0, k = 0, flag = 0, request;printf("最坏适应算法:请问你要分配多大的内存\n");scanf("%d", &request);for (i = 0;i < MI_SIZE - 1 && MList[i].status != 'e';i++){if (MList[i].Size >= request && MList[i].status == 'f') {flag = 1;if (MList[i].Size > k){k = MList[i].Size;j = i;}}}i = j;if (flag == 0){printf("没有足够大小的空间分配\n");j = i;}else if (MList[i].Size - request <= 0){MList[i].status = 'u';}else{for (j = MI_SIZE - 2;j > i;j--){MList[j + 1] = MList[j];}MList[i + 1].start = MList[i].start + request;MList[i + 1].Size = MList[i].Size - request;MList[i + 1].status = 'f';MList[i].Size = request;MList[i].status = 'u';}Display();}void M_Release() //释放内存{int i, number;printf("\n请问你要释放哪一块内存:\n");scanf("%d", &number);if (MList[number].status == 'u'){MList[number].status = 'f';if (MList[number + 1].status == 'f')//右边空则合并{MList[number].Size += MList[number].Size;for (i = number + 1; i < MI_SIZE - 1 && MList[i].status != 'e'; i++) { //i后面的每一个结点整体后移if (i > 0){MList[i] = MList[i + 1];}}}if (number > 0 && MList[number - 1].status == 'f')//左边空则合并{MList[number - 1].Size += MList[number].Size;for (i = number; i < MI_SIZE - 1 && MList[i].status != 'e'; i++){MList[i] = MList[i + 1];}}}else{printf("该块内存无法正常释放\n");}Display();}void BestFit(){int i, j = 0, t, flag = 0, request;printf("最佳适应算法:请问你要分配多大的内存\n");scanf("%d", &request);t = MI_SIZE;for (i = 0; i < MI_SIZE && MList[i].status != 'e'; i++){if (MList[i].Size >= request && MList[i].status == 'f'){flag = 1;if (MList[i].Size < t){t = MList[i].Size;j = i;}}}i = j;if (flag == 0){printf("没有足够大小的空间分配\n");j = i;}else if (MList[i].Size - request <= 0){MList[i].status = 'u';}else {for (j = MI_SIZE - 2; j > i; j--){MList[j + 1] = MList[j];}MList[i + 1].start = MList[i].start + request;MList[i + 1].Size = MList[i].Size - request;MList[i + 1].status = 'f';MList[i].Size = request;MList[i].status = 'u';}Display();}int main(){int x;InitMList();while (1){printf(" \n"); printf(" 1.首次适应\n");printf(" 2.最佳适应\n");printf(" 3.最差适应\n"); printf(" 4.释放内存\n"); printf(" 5.显示内存\n"); printf(" 6.退出系统\n"); printf("请输入1-6:");scanf("%d", &x);switch (x){case 1:FirstFit();break;case 2:BestFit();break;case 3:BadFit();break;case 4:M_Release();break;case 5:Display();break;case 6:exit(0);}}return 0;}实验测试结果记录:1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:1最先适应算法:请问你要分配多大的内存10---------------------------------------------------块号始地址大小状态---------------------------------------------------0 0 10 使用1 10 90 空闲----------------------------------------------1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:1最先适应算法:请问你要分配多大的内存25---------------------------------------------------块号始地址大小状态---------------------------------------------------0 0 10 使用1 10 25 使用2 35 65 空闲----------------------------------------------1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:1最先适应算法:请问你要分配多大的内存15---------------------------------------------------块号始地址大小状态---------------------------------------------------0 0 10 使用1 10 25 使用2 35 15 使用3 50 50 空闲----------------------------------------------1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:1最先适应算法:请问你要分配多大的内存20---------------------------------------------------块号始地址大小状态---------------------------------------------------0 0 10 使用1 10 25 使用2 35 15 使用3 50 20 使用4 70 30 空闲----------------------------------------------1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:4请问你要释放哪一块内存:---------------------------------------------------块号始地址大小状态---------------------------------------------------0 0 10 空闲1 10 25 使用2 35 15 使用3 50 20 使用4 70 30 空闲----------------------------------------------1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:4请问你要释放哪一块内存:2---------------------------------------------------块号始地址大小状态---------------------------------------------------0 0 10 空闲1 10 25 使用2 35 15 空闲3 50 20 使用4 70 30 空闲----------------------------------------------1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:2最佳适应算法:请问你要分配多大的内存5---------------------------------------------------块号始地址大小状态---------------------------------------------------0 0 5 使用1 5 5 空闲2 10 25 使用3 35 15 空闲4 50 20 使用5 70 30 空闲----------------------------------------------1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:3最坏适应算法:请问你要分配多大的内存25---------------------------------------------------块号始地址大小状态---------------------------------------------------0 0 5 使用1 5 5 空闲2 10 25 使用3 35 15 空闲4 50 20 使用5 70 25 使用6 95 5 空闲----------------------------------------------1.首次适应2.最佳适应3.最差适应4.释放内存5.显示内存6.退出系统请输入1-6:总结与自评:总结:分区存储管理是操作系统进行内存管理的一种方式。
头歌数据结构课程设计答案链表动态内存分配

头歌数据结构课程设计答案链表动态内存分配对于链表的动态内存分配,可以采用以下方法:1. 首先需要定义一个链表结构体,包括数据元素和后继指针。
2. 通过调用malloc函数实现动态内存分配,分配所需的节点内存。
3. 在节点内存中存储数据元素和后继指针信息。
4. 将新分配的节点插入到链表中,更新前驱节点的后继指针和当前节点的后继指针。
5. 删除节点时,先保存下一个节点的地址,然后释放当前节点的内存,再将前驱节点的后继指针指向下一个节点。
以下是简单的实现代码:```c//定义链表结构体typedef struct Node {int data;//数据元素struct Node* next;//指向后继节点的指针}Node, *LinkList;//创建链表LinkList createList() {LinkList head = NULL;//头指针初始化为空Node *p, *s;int x;scanf("%d", &x);//输入节点的数据元素while (x != 0) {s = (Node*)malloc(sizeof(Node));//分配节点内存s->data = x;//存储节点的数据元素if (head == NULL) {//链表为空时head = s;p = head;//当前节点为头节点}else {//链表不为空时p->next = s;//将新节点插入到链表尾部p = s;//当前节点成为尾节点}scanf("%d", &x);}p->next = NULL;//最后一个节点后继指针为空return head;}//删除链表中的节点void deleteNode(LinkList list) {Node* p = list;while (p->next != NULL) {//循环查找节点if (p->next->data == x) {//找到了需要删除的节点Node* q = p->next;p->next = q->next;//删除节点free(q);//释放内存return;}p = p->next;//指向下一个节点}}```以上是链表动态内存分配的简单实现,可以根据具体需求进行修改和扩展。
关于动态(长度不定)结构体数组的两种处理方法

关于动态(长度不定)结构体数组的两种处理方法1.使用指针和动态内存分配来处理动态结构体数组在处理动态(长度不定)结构体数组时,一种常见的方法是使用指针和动态内存分配来实现。
下面是具体的步骤:1.1声明结构体类型和指针变量首先,需要定义一个结构体类型,该结构体包含需要存储的数据。
然后,声明一个指向该结构体类型的指针变量。
```ctypedef structint data;} Element;Element *array;```1.2动态内存分配在需要创建结构体数组时,使用C函数malloc(来动态分配内存空间。
为了确定分配的内存大小,可以根据需求计算所需的空间大小。
假设要创建一个包含n个元素的动态结构体数组,则所需的内存空间大小为sizeof(Element) * n。
```cint n; // 数组的长度array = (Element*)malloc(sizeof(Element) * n);```1.3访问结构体数组元素通过指针变量和索引来访问动态结构体数组中的元素。
```carray[i].data = 10; // 给第i个元素的data成员赋值```1.4释放内存在使用完动态结构体数组后,应该释放之前分配的内存空间,以避免内存泄漏。
```cfree(array); // 释放内存空间```2.使用链表来处理动态结构体数组另一种处理动态结构体数组的方法是使用链表数据结构来实现。
链表允许动态添加、删除和访问元素,无需提前知道数组的长度。
2.1定义结构体类型和链表节点类型首先,定义一个结构体类型,该结构体包含要存储的数据。
然后,声明一个链表节点类型,包含结构体类型的指针和指向下一个节点的指针。
```ctypedef structint data;} Element;typedef struct NodeElement *element;struct Node *next;} Node;```2.2创建链表在创建链表时,可以使用头节点来标识链表的开始。
单片机动态分配内存

单片机动态分配内存
动态内存分配可以通过函数库来实现,比如C语言中的malloc()和free()函数。
当程序需要动态分配内存时,可以调用malloc()函数来分配一定大小的内存空间,当不再需要这部分内存时,可以调用free()函数将其释放。
这样可以在程序运行过程中灵活地管理内存,提高内存的利用率。
在单片机中动态分配内存需要考虑一些问题。
首先,单片机的内存资源通常比较有限,动态分配内存可能会导致内存碎片化和内存泄漏的问题,因此需要谨慎使用动态内存分配。
其次,动态内存分配需要考虑内存的分配和释放的效率,避免频繁的内存分配和释放操作影响系统的性能。
另外,由于单片机系统的资源有限,需要合理规划内存的使用,避免内存耗尽导致系统崩溃。
在实际应用中,可以根据单片机系统的具体情况和应用需求,合理选择动态内存分配的策略,比如采用内存池管理的方式来优化动态内存分配的效率和资源利用率。
同时,需要注意动态内存分配可能带来的风险,比如内存泄漏和内存溢出等问题,需要进行严格的内存管理和测试验证。
总之,单片机动态分配内存是一项复杂的任务,需要综合考虑系统资源、性能和安全等方面的因素,合理设计和使用动态内存分配功能,以实现系统的稳定和高效运行。
动态分区分配最佳最坏适应算法

动态分区分配最佳最坏适应算法动态分区分配是一种内存分配技术,它将内存空间划分为若干个不同大小的分区,并根据进程的内存需求,分配适当大小的分区给进程使用。
常用的动态分区分配算法有最佳适应算法、最坏适应算法和首次适应算法。
下面将详细介绍这三种算法及其特点。
1.最佳适应算法:最佳适应算法是根据进程所需内存大小,选择最佳大小的空闲分区进行分配。
具体操作是扫描所有空闲分区,选择满足进程内存需求且大小最小的分区进行分配。
这样可以最大化地利用内存,避免了大块内存被小进程占用,但是可能会出现很多碎片,影响内存空间的利用效率。
2.最坏适应算法:最坏适应算法是根据进程所需内存大小,选择最坏大小的空闲分区进行分配。
具体操作是扫描所有空闲分区,选择满足进程内存需求且大小最大的分区进行分配。
这样可以减少外部碎片的产生,但是可能会导致内存空间利用率较低,大块内存可能会被小进程占用。
3.首次适应算法:首次适应算法是根据进程所需内存大小,选择第一个满足需求的空闲分区进行分配。
具体操作是从内存的起始地址开始扫描所有空闲分区,选择满足进程内存需求的第一个分区进行分配。
这样可以快速地找到合适的分区进行分配,但可能会导致外部碎片的产生。
这三种算法各有优缺点,最佳适应算法可以最大化地利用内存空间,但是可能会产生很多碎片;最坏适应算法可以减少碎片的产生,但是可能会导致内存空间利用率较低;首次适应算法在查找分区时较为迅速,但也容易产生外部碎片。
为了更好地利用内存空间并降低碎片发生的概率,还可以使用一些优化策略,例如合并相邻的空闲分区,释放不再使用的分区等。
此外,还可以采用其他的内存分配算法,如循环首次适应算法、随机适应算法等,以根据具体情况选择适当的算法。
总之,动态分区分配是一种常用的内存管理技术,可以根据进程的内存需求进行灵活的分配。
不同的分配算法有不同的特点,可以根据具体需求选择适合的算法,并结合相应的优化策略,以提高内存利用效率和系统性能。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
浅析动态内存分配及Malloc/free的实现2011-03-18 22:47一、概述:
动态内存分配,特别是开发者经常接触的Malloc/Free接口的实现,对许多开发者来说,是一个永远的话题,而且有时候也是一个比较迷惑的问题,本文根据自己的理解,尝试简单的探究一下在嵌入式系统中,两类典型系统中动态内存分配以及Malloc/Free的实现机制。
二、内存分配方式
Malloc/Free主要实现的是动态内存分配,要理解它们的工作机制,就必须先了解操作系统内存分配的基本原理。
在操作系统中,内存分配主要以下面三种方式存在:
(1)静态存储区域分配。
内存在程序编译的时候或者在操作系统初始化的时候就已经分配好,这块内存在程序的整个运行期间都存在,而且其大小不会改变,也不会被重新分配。
例如全局变量,static变量等。
(2)栈上的内存分配。
栈是系统数据结构,对于进程/线程是唯一的,它的分配与释放由操作系统来维护,不需要开发者来
[url=javascript:;]管理[/url]。
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元会被自动释放。
栈内存分配运算内置于处理器的指令集中,效率很高,不同的操作系统对栈都有一定的限制。
(3)堆上的内存分配,亦称动态内存分配。
程序在运行的期间用malloc申请的内存,这部分内存由程序员自己负责管理,其生存期由开发者决定:在何时分配,分配多少,并在何时用free来释放该内存。
这是唯一可以由开发者参与管理的内存。
使用的好坏直接决定系统的性能和稳定。
三、动态内存分配概述
首先,对于支持虚拟内存的操作系统,动态内存分配(包括内核加载,用户进程加载,动态库加载等等)都是建立在操作系统的虚拟内存分配之上的,虚拟内存分配主要包括:
1、进程使用的内存地址是虚拟的(每个进程感觉自己拥有所有的内存资源),需要经过页表的映射才能最终指向系统实际的物理地址。
2、主内存和磁盘采用页交换的方式加载进程和相关数据,而且数据何时加载到主内存,何时缓存到磁盘是OS调度的,对应用程序是透明的。
3、虚拟存储器给用户程序提供了一个基于页面的内存大小,在32位系统中,用户可以页面大小为单位,分配到最大可以到4G(内核要使用1G或2G等内存地址)字节的虚拟内存。
4、对于虚拟内存的分配,操作系统一般先分配出应用要求大小的虚拟内存,只有当应用实际使用时,才会调用相应的操作系统接口,为此应用程序分配大小以页面为单位的实际物理内存。
5、不是所有计算机系统都有虚拟内存机制,一般在有MMU硬件支持的系统中才有虚拟内存的实现。
许多嵌入式操作系统中是没有虚拟内存机制的,程序的动态分配实际是直接针对物理内存进行操作的。
许多典型的实时嵌入式系统如Vxworks、Uc/OS 等就是这样。
四、动态内存分配的实现
由于频繁的进行动态内存分配会造成内存碎片的产生,影响系统性能,所以在不同的系统中,对于动态内存管理,开发了许多不同的算法(具体的算法实现不想在这里做详细的介绍,有兴趣的读者可以参考Glib C 的源代码和附录中的资料)。
不同的操作系统有不同的实现方式,为了程序的可移植性,一般在开发语言的库中都提供了统一接口。
对于C语言,在标准C库和Glib 中,都实现了以malloc/free为接口的动态内存分配功能。
也就是说,malloc/free库函索包装了不同操作系统对动态内存管理的不同实现,为开发者提供了一个统一的开发环境。
对于我们前面提到的一些嵌入式操作系统,因为实时系统的特殊要求(实
时性要求和开发者订制嵌入式系统),可能没有提供相应的接口。
一般C 库中的malloc/free 函数会实现应用层面的内存管理算法,在系统真正需要内存时,才通过操作系统的API(系统调用)来获取实际的物理内存,当然,你也可以使用第三方的内存管理器,或者通过自己改写malloc/free函数来实现应用层面的内存管理。
4.1、动态内存管理的一般机制:
动态内存管理机制会随操作系统和系统架构的不同而不同,一些操作系统利用malloc等分配器,在支持虚拟内存的操作系统中就利用这种方式来实现进程动态内存管理的。
而另外一些系统利用预先分配的内存区间来进行动态内存管理,该方式主要应用于不支持虚拟内存机制的嵌入式操作系统中。
对于进程动态内存管理机制的具体实现,许多系统提供了统一的接口,并由malloc/free函数来具体实现。
下面以嵌入式Linux和Uc/OS 为例,简单解析一下动态内存管理的具体实现。
4.2、Linux下动态内存分配的实现:
在Linux下,glibc 的malloc提供了下面两种动态内存管理的方法:堆内存分配和mmap 的内存分配,此两种分配方法都是通过相应的Linux 系统调用来进行动态内存管理的。
具体使用哪一种方式分配,根据glibc的实现,主要取决于所需分配内存的大小。
一般情况中,应用层面的内存从进程堆中分配,当进程堆大小不够时,可以通过系统调用brk来改变堆的大小,但是在以下情况,一般由mmap系统调用来实现应用层面的内存分配:A、应用需要分配大于1M的内存,B、在没有连续的内存空间能满足应用所需大小的内存时。
(1)、调用brk实现进程里堆内存分配
在glibc中,当进程所需要的内存较小时,该内存会从进程的堆中分配,但是堆分配出来的内存空间,系统一般不会回收,只有当进程的堆大小到达最大限额时或者没有足够连续大小的空间来为进程继续分配所需内存时,才会回收不用的堆内存。
在这种方式下,glibc 会为进程堆维护一些固定大小的内存池以减少内存脆片。
(2)、使用mmap的内存分配
在glibc中,一般在比较大的内存分配时使用mmap系统调用,它以页为单位来分配内存的(在Linux中,一般一页大小定义为4K),这不可避免会带来内存浪费,但是当进程调用free释放所分配的内存时,glibc会立即调用unmmap,把所分配的内存空间释放回系统。
注意:这里我们讨论的都是虚拟内存的分配(即应用层面上的内存分配),主要由glibc来实现,它与内核中实际物理内存的分配是不同的层面,进程所分配到的虚拟内存可能没有对应的物理内存。
如果所分配的虚拟内存没有对应的物理内存时,操作系统会利用缺页机制来为进程分配实际的物理内存。
4.3、Uc/OS 下内存分配的实现:
在一般的实时嵌入式系统中,由于实时性的要求,很少使用虚拟内存机制。
所有的内存都需要开发人员参与分配,他们直接操作物理内存,所分配的内存不能超过系统的物理内存,于系统的堆栈的管理,都由开发者显式进行。
在Uc/OS 中,主要利用内存分区来管理系统内存,系统中一般有多个分区,每个分区相当于一些固定大小内存块的内存池,应用可以从这些内存池中分配内存,当内存使用完成后,也需要把该内存释放回对应的内存池中。
Uc/OS 的内存管理可以分为以下过程:
(1)、创建内存分区:在使用内存之前,开发者必须首先调用OSMemCreare()函数来创建相应的内存分区,在创建内存分区成功后,就会在系统中存在一个以开发者指定内存大小,指定内存块数目的内存池。
在此过程中,开发者需要明确的知道系统的内存分布,并指明内存池的基址。
(2)、申请内存:当系统内存分区创建好了后,系统就可以从相应的内存分区中获取内存了。
在Uc/OS 中,主要利用OSMemGet()来申请内存,应用程序会根据所需要内存的大小,从开发
者指定的内存池中申请内存。
(3)、释放内存:因为内存是系统的紧缺资源,当应用不再需要使用所申请的内存时,应该及时释放该内存。
在Uc/OS 中,主要利用OSMemPut()来释放不再需要的内存,在此过程中,开发者应该保证把该内存释放回原内存的分区。
在一些实时嵌入式系统中,系统也会提供一些比较复杂的内存管理机制,并为应用提供类malloc/free接口供开发者使用,如Vxworks等。
不管怎么样,在这些系统中,都得由开发者显式的参与内存的管理,而且应用程序只能使用实际物理内存大小的内存。
五、小结:
动态内存管理是开发者唯一能够参与管理的内存分配机制,这给开发者灵活使用系统主存提供了一个手段,但是由于内存的分配和释放都得依靠开发者显式进行,很容易出现内存泄露的问题。
另外,不好的动态内存管理算法对系统的性能影响有着也不可忽视影响。
在本文简单解析了两种动态内存管理实现,它们互有忧缺点:对于以虚拟内存机制和分页机制为基础的动态内存管理(如Linux等),由于请求分页机制的存在,不能满足系统实时性方面的要求,但是它能为应用提供最多能到4G的内存空间,而且由于应用虚拟内存机制,可以为进程空间提供保护,一个进程的崩溃不会影响其他的进程。
对于基于非虚拟内存管理机制的系统,由于可以直接操作物理内存,提高了系统实时性,在这样的系统中,开发者的参与度比基于虚拟内存机制的要高。
其缺点是没有进程间的保护机制,一个进程或任务的错误很容易导致整个系统的崩溃。
参考:
1、Glibc 源代码
2、嵌入式实时操作系统Uc/OS-II
3、经典收藏之 - C++内存管理详解:
/xxyd/jzjs/aspnet/200703/14963.html
4、Understanding Linux kernel
5、The Virtual-Memory Manager in Windows NT:
/en-us/library/ms810616.aspx。