C语言--动态内存管理1
目录
- 前言
- 动态内存函数介绍
- malloc
- free
- calloc
- realloc
- 常见的动态内存错误
- 对NULL指针的解引用操作
- 对动态开辟空间的越界访问
- 对非动态开辟内存使用free释放
- 使用free释放一块动态开辟内存的一部分
- 对同一块动态内存多次释放
- 动态开辟内存忘记释放(内存泄漏)
- 对通讯录进行优化
前言
我们目前已知的内存开辟方式是要指定长度的,比如以下这两种方式
int val = 20;
char arr[10] = {0};
我们发现:
- 这些空间开辟的大小时固定的,无法被修改
- 如果是数组,在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配.
再比如我们的通讯录,如果我们一开始就确定了人员个数的最大值,那么一旦到达这个上限,就无法继续录入数据了,我们能不能实现一种能根据我们的需求灵活更改空间大小的方式呢?这时候我们就需要动态内存开辟空间了。
动态内存函数介绍
malloc
我们先来看书写的格式:
void* malloc (size_t size);
malloc本质上是一个函数,它的作用是开辟内存块,它有参数,有返回值,我们现在来看看分别是什么:
1. 如果开辟空间成功,则返回一个指向开辟好空间的指针。
2. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
因为malloc这个函数的返回值是void类型的指针,所以我们在接收的时候,要强制力类型转换为我们想要的类型,并用相应的指针来接收。比如我们要申请5个整型*的内存空间,代码就应这样书写:
int* p=(int)malloc(20);//注意这里的20是指20个字节,即5个整型大小
值得注意的是,此时我们申请的动态内存空间是在堆区的,这有别于我们之前在栈区开辟空间。
3. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
我们知道,对空指针解引用操作是很危险的,所以我们一定要在使用前检查一下,代码如下
//申请
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
4. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
free
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的.
书写格式如下:
void free (void* ptr);
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
我们将 p free掉后看一下p的地址是否发生改变。
我们发现,虽然空间被回收了,但是p内存放的地址并没有改变,这时候如果再对其解引用便是非法访问了,所以我们要手动将p的值赋为NULL,具体代码实现如下
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
free(p);
p = NULL;
return 0;
}
备注:malloc和free都声明在 stdlib.h 头文件中。
calloc
C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的**每个字节初始化为全0。
假设我们要申请十个int类型大小的空间,书写方式如下
int *p = (int*)calloc(10, sizeof(int));
realloc
我们看这个函数的前缀re,联系英语常识可以大概猜到这个函数的用途是重新,再次开辟一块空间。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整。
书写格式如下
void* realloc (void* ptr, size_t size);
1. ptr 是要调整的内存地址。
2. size 是调整之后新大小。
3. 返回值为调整之后的内存起始位置。
要注意区分的是:realloc在调整内存空间的是存在两种不同情况,分别是原有空间之后有足够大的空间和原有空间之后没有足够大的空间。
情况1: 原有空间之后有足够大的空间
此时,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2: 原有空间之后没有足够大的空间
此时,realloc会找更大的空间(找不到就开辟失败了),将原来的数据拷贝到新的空间去,释放旧的空间,返回新空间的地址。
常见的动态内存错误
对NULL指针的解引用操作
我们来看下面这段代码
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;
free(p);
}
我们在前面讲到过,如果malloc函数开辟空间失败,就会返回空指针,对空指针解引用很明显存在问题。
对动态开辟空间的越界访问
void test()
{
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);
}
上面这段代码当i=10的时候,已经超出我们开辟的空间范围了,这时候再解引用就会越界访问。
对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);
}
我们前面说过: 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。所以这里我们对非动态开辟内存使用free释放肯定是行不通的。
使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);
}
看这段代码我们发现,p++使得p不再指向动态内存开辟的起始位置,这样写是有问题的。
对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);
}
这样写是有问题的,但是如果我们释放完一次就将p赋值为空指针,这时候再对p free就不会有任何问题了。
void test()
{
int *p = (int *)malloc(100);
free(p);
p=NULL;
free(p);
}
我们在前面也讲过,如果参数 ptr 是NULL指针,则函数什么事都不做。
动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
我们首先要了解一点的是:以上的所有函数所申请的空间,如果不想使用,需要free释放,如果不想使用free释放,在程序结束之后,也会由操作系统回收。
但是,如果不使用free释放,程序也不结束,就会造成内存泄露。
这里尤其要注意的点是,如果动态内存函数开辟空间是在另一个函数内部进行的,出了这个函数,这块空间是没有像局部变量一样被回收的,只有free能将它回收!!!
对通讯录进行优化
在学习了动态内存函数后,我们可以将其灵活运用到通讯录中,实现可以对达到人数上限的通讯录进行扩容。
主要修改点在于
1,初始化
我们假定初始大小为3,每次扩容大小为2。这里我们可以用宏,方便修改。
同时,因为我们需要对当前容量与最大容量比较决定是否扩容,所以还需一个参数capacity来存放最大容量的的数值。
#define DEFAULT_SZ 3
#define INC_SZ 2
void InitContact(Contact* pc)
{
pc->sz = 0;
pc->data = malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (pc->data == NULL)
{
printf("通讯录初始化失败:%s", strerror(errno));
return;
}
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
}
2,扩容
void CheckCapacity(Contact* pc)
{
if (pc->sz == pc->capacity)//判断是否达到容量上限
{
PeoInfo* ptr = realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
if (ptr == NULL)
{
printf("扩容失败:%s\n",strerror(errno));
return;
}
else
{
pc->data = ptr;
pc->capacity += INC_SZ;
printf("扩容成功,当前容量:%d\n", pc->capacity);
}
}
}
3,销毁
void DestroyContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
以上就是本章全部内容,如有出入,欢迎指正。