那么不废话,让我们直接开始~~~
接下来我会从大致这三个方面来讲解动态内存管理:
1.动态内存管理的基本概念
动态内存管理:它是C语言中一项重要的编程任务,它使得程序在运行时能够灵活地分配和释放内存,更好地适应不同的运行条件,通过动态内存管理,我们可以使用内存更高效、更灵活。
为了让读者能够更好的理解,现将其拆解开来理解:
动态:即我们可以将申请的空间自由的变大变小以适应需求;
内存:即我们申请的对象是内存空间;
管理:我们需要对申请的内存空间进行管理操作;
2.为什么要有动态内存管理
首先让我们来看一个实例:
int n = 1; //在栈空间上开辟4个字节 int arr[5] = { 0 }; //在栈空间上开辟20个字节的连续空间
从上边的实例我们可以看出,我们创建变量是开辟空间的大小是固定的;当我们在船舰数组的时候,必须指定数组的长度,并且数组空间一旦确定了大小将不能调整其大小。但是有时候我们需要的空间大小是在程序运行的时候才能知道,那么上述创建空间大小的方式就不太适合了。
因此引入了动态内存开辟的方式,让程序员自己可以申请和释放空间,这也就是为什么要有动态内存管理的原因。
3.动态内存的申请和释放
现在我们已经了解动态内存管理的基本概念和为什么要有动态内存管理,那么接下来就是如何去申请动态内存,并且对其进行操作了。
对于申请和释放动态内存空间,我们有这三个函数实现:malloc、free、calloc、realloc
1.malloc
先让我们看一下官网对malloc函数的解释:
解释如下:
1.该函数是向内存申请一块连续可用的空间,并返回指向这块空间的指针(首位置的地址);
2.如果开辟空间成功,则返回一个指向开辟好空间的指针;
3.如果开辟失败,则返回一个NULL 指针;
4.申请空间的大小的单位是字节,如果参数size 为0,malloc的行为是标准是未定义的,取决于编译器。
5.使用该函数需要包含<stdlib.h>头文件
接下来让我们使用一个实例助你更好的了解malloc函数(如图):
//向内存申请20个字节大小的空间,并放入5个整数 #include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(20); if (p == NULL) { perror("malloc"); return 1; } for (int i = 0; i < 5; i++) { *(p + i) = i + 1; } for (int k = 0; k < 5; k++) { printf("%d ", *(p + k)); } return 0; }
这里有两个注意点需要我们注意:
1.我们将malloc的返回值进行了强制转换(返回值是void * 类型,但是我们将其强制转换成了int *,原因是我们已经知道了申请的空间中我们要放入什么数据,为了便于下面的操作,我们进行了对指针的强制转换);
2.使用指针接收返回值后,我们判断了指针是否为NULL(原因是如果开辟失败,则返回一个NULL 指针,我们不能对NULL指针进行操作,所以直接停止程序);
至此我们就大致的了解了malloc函数的使用。
2.free
有借有还,再借不难,我们向内存申请空间使用完成之后,就需要将申请的空间归还给操作系统,那么我们就需要使用free函数。
先让我们看一下官网对malloc函数的解释:
用一句话解释就是如果想要释放申请的空间,就将接收申请空间的地址的指针填入即可();
注:想要使用free函数释放申请内存空间,必须传递接收申请空间的地址的指针(即申请空间时返回值的地址,不能放入其他位置的地址)
现在我们将上面的例子改进一下:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(20); if (p == NULL) { perror("malloc"); return 1; } for (int i = 0; i < 5; i++) { *(p + i) = i + 1; } for (int k = 0; k < 5; k++) { printf("%d ", *(p + k)); } free(p); p = NULL; return 0; }
从上面代码我们发现free完申请的空间之后,我们将接收申请空间的地址的指针置为了NULL,原因是放置该指针变为野指针。
至此我们就大致的了解了free函数的使用。
3.calloc
calloc函数的使用以及用处和malloc大致相同,只有些许细节略微有些不同。
先让我们看一下官网对calloc函数的解释:
仿照着malloc函数,我们对calloc函数解释一下:
解释如下:
1.函数的功能是开辟一块大小为num 个大小为size 的内存空间,返回指向这块空间的指针,并且把空间的每个字节初始化为0;
2.如果开辟空间成功,则返回一个指向开辟好空间的指针(首位置的地址);
3.如果开辟失败,则返回一个NULL 指针;
4.申请空间的大小的单位是字节,如果参数size 为0,malloc的行为是标准是未定义的,取决于编译器。
5.使用该函数需要包含<stdlib.h>头文件
我们使用一个实例进行进一步加深理解:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)calloc(5, sizeof(int)); if (p == NULL) { perror("malloc"); return 1; } for (int i = 0; i < 5; i++) { *(p + i) = i + 1; } for (int k = 0; k < 5; k++) { printf("%d ", *(p + k)); } free(p); p = NULL; return 0; }
事实上calloc函数在使用的形式上只是函数名称和参数改变了一下,其他与malloc函数全部一样。
注意:
malloc函数和calloc函数内部的不同体现在了是否会把申请的内存空间的每个字节初始化为0!!!
如图所示:
malloc函数对申请的内存空间不进行初始化:
malloc函数对申请的内存空间进行初始化为0:
以上即malloc函数和calloc函数在存储内部的不同!!!
4.realloc
讲了这么多对动态内存空间的申请,但是不是说可以对申请的空间大小进行修改吗?那么如何修改呢?
这就需要使用realloc函数:
先让我们看一下官网对realloc函数的解释:
我们对该函数进行讲解:
讲解如下:
1.该函数会返回修改之后的内存空间的地址;
2.ptr 是要调整的内存地址(即申请动态内存空间时返回值的地址),size 为调整之后新大小;
3.如果修改空间成功,则返回一个指向修改好的空间的指针(首位置的地址);
4.如果修改失败,则返回一个NULL 指针;
但是既然我们想要修改申请内存空间的大小,就可能会增加申请空间的大小,但由于我们申请的空间是连续的,就可能会遇到想要扩增空间时,后面的空间被使用的情况(如图):
那么这个时候该怎么扩增空间呢?
所以我们就需要分开进行讨论:
1.后续空间充足时:
要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化
2.后续空间不充足时:
其扩展的方法是:在堆空间上另找一个合适大小的连续空间,将已有数据先复制过去后,在进行扩增,然后返回一个新的内存地址。
以上我将就了解了realloc函数的使用和其是如何进行扩增空间的。那么我们接下来使用一个实例来使你更好的理解realloc函数:
//向内存申请20个字节大小的空间,并放入5个整数之后, //将空间扩增为40字节大小,并接着上面的数字继续进行赋值 #include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(20); if (p == NULL) { perror("malloc"); return 1; } for (int i = 0; i < 5; i++) { *(p + i) = i + 1; } for (int k = 0; k < 5; k++) { printf("%d ", *(p + k)); } printf("\n"); int* pc = (int*)realloc(p, 40); if (pc != NULL) { p = pc; pc = NULL; for (int i = 5; i < 10; i++) { *(p + i) = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", *(p + i)); } } free(p); p = NULL; return 0; }
从上面代码我们可以看到我们扩增完空间后,判断了其接收的指针是否是NULL之后才继续让p指针管理扩增后的空间,这样做的目的是为了防止扩增失败后返回NULL,可能会将之前管理内存空间的指针变为NULL,使之前开辟的空间失效。
以上我们就理解了realloc函数。
总结
内存管理是一项非常重要的任务。动态内存管理是指在程序运行时分配和释放内存的过程。通过动态内存管理,我们可以根据需要分配适当的内存空间,并在不再需要时释放它。这使得程序更加灵活,并能够处理各种大小和形状的数据。
以上就是动态内存管理的所有内容了~~~