为什么要有动态内存管理呢
大家在此前的C语言学习中已经知道,我们要定义一个值,首先要为它在内存空间上开辟一个空间,通常情况下我们用这种方式来开辟:
int val = 20;//在内存空间上开辟四个字节 char arr[10] = {0};//在内存空间上开辟10个字节的空间
但是,以上的开辟空间的方式有以下的特点:
1. 空间开辟⼤⼩是固定的。
2. 数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整
但是通常情况先我们对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,那数组的编译时开辟空间的⽅式就不能满⾜了。
因此,C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就可以使得内存空间变得灵活,同时也可以不浪费空间。
动态内存函数
动态内存函数有以下四个:
malloc free calloc realloc
下面我们将四个函数进行详解:
malloc
malloc的函数原型如下
void* malloc (size_t size)
malloc函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针,并且不知道所开辟空间是什么类型,可能是char,可能是int,所以返回值类型为void*
下面有几个需要注意的点:
1.malloc函数并不是会一直成功的,也就是说,可能会发生malloc函数申请内存空间失败的情况,那么malloc函数就会返回一个空指针,所以我们在使用malloc函数申请空间后,可以使用if语句进行判断是否申请空间成功
例如:我们用malloc函数开辟20个字节的整形空间
这里我们用到了perror函数,可以打印函数的错误信息
int* ptr = malloc(20); if (ptr == NULL) { perror("malloc"); return;//如果开辟失败,程序结束 }
2.如果参数 size 为0,由于malloc函数的⾏为是标准是未定义的,而是取决于编译器,所以,在不同的编译器的会出现不同的情况,部分的编译器可以开辟0个字节的空间
free
函数free,是专⻔是⽤来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr)
free函数有一个特别需要注意的点:
如果参数指向的空间不是动态开辟的,那free函数的⾏为是未定义的
注:在free(ptr)后,我们还可以将其置为空指针,防止ptr变为野指针
什么意思呢?我们举一个例子:
int main() { int a = 1; int* ptr = &a; free(ptr); ptr=NULL; return 0; }
这个代码在运行是就会中断,因为ptr所指向的空间不是动态开辟的
可能有些同学会问,那如果ptr指向的空间为空指针呢,代码跑起来会有错误吗?
例如:
int* ptr=NULL; free(ptr);
答案是不会!如果参数是NULL指针,则函数什么事都不做!
大家还要注意,free和malloc函数的声明都在 stdlib.h 头⽂件中
calloc
calloc 函数也⽤来动态内存分配。原型如下:
void* calloc (size_t num, size_t size)
calloc函数就是为num个大小为size字节的元素开辟一块空间。
大家可能会有疑问,calloc函数和malloc函数没有区别啊?
其实不然,calloc函数开辟空间之后还会将num个元素全部初始化为0!
我们用代码验证一下:
用calloc函数开辟十个整形大小的空间,并将其打印
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)calloc(10, sizeof(int)); if (NULL != p) { int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } } free(p); p = NULL; return 0; }
打印结果如下:
所以, calloc和malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
realloc
写代码时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤⼩的调整,realloc函数的出现让动态内存管理更加灵活
函数原型如下:
void* realloc (void* ptr, size_t size)
ptr 是要调整的内存地址, size 是调整内存之后内存空间的新⼤⼩,返回值为调整之后的内存起始位置
调整原内存空间⼤⼩的基础上,还会将原内存中的数据移动到新的空间
记住,是新的空间!
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有⾜够⼤的空间
调整方式:
要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化
情况2:原有空间之后没有⾜够⼤的空间
调整方式:
在堆空间上另找⼀个合适⼤⼩的连续空间使⽤,函数返回的是新的内存地址
常⻅的动态内存的错误
讲解完动态内存函数后,我们在日常的代码中可能会出现一些常见的错误,下面我们举几个具体的例子
对NULL指针的解引⽤操作
int *p = (int *)malloc(INT_MAX/4); *p = 20; free(p);
如果我们开辟空间失败后返回了空指针,但是我们后续又对p进行了解引用操作,这样就会出现错误
对动态开辟空间的越界访问
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; } free(p);
我们只为p申请了十个int的空间,而此代码中的for循环却访问了下标为10的元素,出现了越界访问
对⾮动态开辟内存使⽤free释放
int a = 1; int* ptr = &a; free(ptr); ptr=NULL; return 0;
我们的p指向的a并不是动态开辟的内存,所以free就是未定义的
使⽤free释放⼀块动态开辟内存的⼀部分
int *p = (int *)malloc(100); p++; free(p)
这里的p已经进行“++”操作,p不再指向内存起始的位置,并没有释放整个动态内存,而是一部分
对同⼀块动态内存多次释放
int *p = (int *)malloc(100); free(p); free(p);
此代码中连续进行了两次free,会出现bug
但是如果是下面这种情况就无妨了:
因为将p置为空指针了,后续的free也就不起作用了
int *p = (int *)malloc(100); free(p); p=NULL; free(p);
动态开辟内存忘记释放(内存泄漏)
int *p = (int *)malloc(100); if(NULL != p) { *p = 20; }
此代码没有对p进行内存的释放,我们申请了一百个字节的整形空间大小,并没有使用怎么多的空间,虽然操作系统会自动回收内存,但是会产生较多的内部碎片,效率不如free,仍然会有较大的浪费,就产生了内存泄漏
好了,以上就是今天的分享了,谢谢大家!