一、什么是动态内存管理?
一种内存管理方法。对内存空间的分配、回收等操作在进程执行过程中进行,以便更好地适应系统的动态需求,提高内存利用率。
二、为什么需要动态内存管理?
对于初学者而言,已经基本可以掌握的内存开辟方法:定义+初始化的方法。
int a=0; int arr[]={0,1,2,3}
char arr[]={"abcd"}等等
但是上述内存的开辟大小是在编译前就确定的,同时数组在声明的时候又必须指定数组长度, 它所需要的内存在编译的时候分配;【这种开辟方式的特点】
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了,而且盲目的开辟内存只会造成内存的浪费,
【这时候就只能试试动态存开辟了】
三、动态内存函数
1、malloc
定义 : void* malloc (size_t size); (stdlib.h)
作用
向内存申请一段连续的、可用的空间
特点
//返回类型为void*,参数为开辟空间的大小(单位字节)
- 这个函数向内存申请一段连续可用的空间,并返回指向这个空间的指针
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查(assert)。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己 来强制类型转换。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
实例
运行结果为0 1 2 3 4
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(5 * sizeof(int)); for (int i = 0;i < 5;i++) { *(p + i) = i; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
2、free
定义void free(void*str);(stdlib.h)
作用
free是c语言提供的,专门用来释放、回收动态开辟出来的空间的函数。
特点
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的,运行代码的时候将会报错。
- 如果参数 ptr 是NULL指针,则使用free函数将什么事都不不会发生。
(举例同上malloc实例)
3、calloc
定义 void*calloc(size_t num,size_t size)
(stdlib.h)
作用
它的作用同malloc相似,也是向内存申请一段连续的、可用的空间。
特点
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
举例
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(5 * sizeof(int)); printf("p(malloc):"); for (int i = 0;i < 5;i++) { printf("%d ", *(p + i)); }printf("\n\n"); int* c = (int*)calloc(5, sizeof(int)); printf("c(calloc):"); for (int i = 0;i < 5;i++) { printf("%d ", *(c + i)); } printf("\n\n"); free(p);free(c); p = NULL;c=NULL; return 0; }
4、realloc
void*realloc(void*p,size_t size) stdlib,h
作用
重新调整已开辟空间的大小。
realloc函数的出现让动态内存管理更加灵活。在使用malloc或者calloc函数的时候,可能会遇到动态开辟的内存仍然不够的情况,会觉得申请的空间过大或者过小,那为了合理地使用内存,避免再去重新开辟一段内存空间,造成难以维护的情况,那么将会使用到realloc函数。 realloc 函数就可以做到对动态开辟内存大小的调整。
特点
- 指针p是要调整的已经开辟的空间的地址
- size是调整后的大小(单位:字节)
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
- 在使用realloc调整动态内存的时候存在两种不同的开辟方法:
方法1、原有空间之后存在足够的且连续的空间:
方法2、在堆空间中另找一块连续的空间;
情况 1
当是情况 1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况 2
当是情况 2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。
实例
1. int*p=(int*)malloc(5*sizeof(int)); 2. //若空间不够了,使用realloc 3. int*p=(int*)realloc(p,10*sizeof(int));
四、常见动态内存错误
1、动态内存访问越界
类似于数组int arr[10]访问越界:访问了不属于它的空间。
int arr[10]; arr[11]=0;
动态内存开辟的空间同样存在越界访问的问题
案例
int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { exit(EXIT_FAILURE); } for (i = 0; i <= 10; i++) { *(p + i) = i;//当i是10的时候越界访问 } free(p); p = NULL;
2、对NULL空指针进行解引用操作
int * p = ( int * ) malloc ( INT_MAX / 4 );
* p = 20 ; //如果malloc开辟空间失败,则会返回空指针(NULL),此时将空指针赋给p
//解引用后就是对空指针的解引用
这种操作会引起程序或者系统崩溃
3、对非动态开辟内存使用free释放
int a=10; int*p=&a; free(p);
此操作会引起程序崩溃;
4、free未释放动态开辟的内存
1、未使用free函数
这种错误较为隐蔽,错误产生了但是不容易被发现
int *p=(int*)malloc (5*sizeof(int));//未使用free
未使用free不会报错,但是存在内存泄漏,影响程序的正常运行。
2、使用了free函数,但只释放了部分内存
int* p = (int*)malloc(5 * sizeof(int));
p++; //此处将p的地址向后挪了一位;
free(p);
p=NULL;
- 当p的所指向的位置向后移位的时候,free只释放了部分动态开辟内存,仍存在一部分内存没有被回收,也是内存泄漏的一种。 此时运行程序就会发生崩溃。
5、对一块动态开辟内存进行多次释放
int *p=(int*)malloc (5*sizeof(int)); //假设开辟成功
free(p);
free(p);
同样会报错。
解释:当你将p free释放的时候,p指向的这块空间已经不属于malloc等函数动态开辟的空间,系统可能将这块空间分配给了其他程序,这时在此使用free就相当于释放了非动态开辟内存,就会同上面一样报错。
总结
此文章讲解了什么是动态内存管理,以及一些常用的动态内存管理的函数,和常见错误