目录
一、为什么存在动态内存管理
二、动态内存函数的介绍
1、malloc
2、free
3、calloc
4、realloc
三、常见的动态内存错误
1、对NULL指针的解引用操作
2、对动态开辟的内存的越界访问
3、对非动态开辟内存使用free释放
4、使用free释放动态开辟内存的一部分
5、对同一块动态内存多次释放
一、为什么存在动态内存管理
看以下代码:
int a = 10;
int arr[10] = { 0 };
第一个是向空间申请了4个字节,第二个是向空间申请了40个字节。
这样开辟空间的方式有2个特点:
1、空间开辟大小是固定的
2、数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配
想一想,我们在写课设作业的时候,比如写了一个音乐管理系统,对音乐信息的增删查改,那我们需要给音乐信息开辟空间,用数组的方式开辟了100个空间,那如果系统要存放200首音乐信息,空间就不够用,如果只需要存放50首歌曲信息,我们开辟的100首的信息空间就浪费了。对于空间的需求,有时候我们需要的空间大小在程序运行时侯才能知道,那数组在编译时开辟的空间的方式就不能满足了,所以在这里,我们需要一种更加灵活的开辟空间的方法——动态内存管理。
二、动态内存函数的介绍
在msdn里可以查找以下函数的定义及使用
以下函数在使用时,需要包含一个头文件
#include
1、malloc
函数解释:
这个函数可以帮我们向内存申请一块连续可用的空间,并且返回指向这块空间的指针
使用注意:
1、如果开辟成功,返回指向开辟好的空间的地址
2、如果开辟失败,返回一个NULL指针,因此malloc的返回值需要判断是否为空指针后再使用
3、返回值是void*,在使用时需要我们自己来决定开辟空间的类型
注:为什么是void*,因为这个函数不知道申请者需要什么什么类型,它对开辟后的空间的用途是未知的,所以返回void*,具体什么用途什么类型都得看申请者自己
4、如果参数size为0,malloc的行为是标准未定义的,取决于编译器
下面看一个例子:
#include#include int main() { int* p = (int*)malloc(40); if (p == NULL) { printf("开辟失败n"); } else { int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; printf("%d ", p[i]); } printf("n"); } return 0; }
代码分析:
1、malloc开辟的是字节数,返回的是开辟的空间的指针(void*),而我们现在需要的是int的数据,所以需要 先强制类型转换为int*的指针,然后再用指针变量p来接收。
2、判断是否开辟成功,检查p是否为空指针
3、开辟成功,开始赋值,*(p+i)与p[i]是等价的
看结果:
补充一点小知识:
动态内存管理的函数开辟的内存都是在堆区上开辟的,关于堆区,浅浅普及一下:
堆区的内存空间由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
大家会注意到,它分配的空间是由程序员自己释放的,所以就需要认识下一个函数
free
2、free
专门是用来做动态内存的释放和回收的
参数:内存空间的指针
free用来释放动态开辟的内存
1、如果参数memblock指向的空间不是动态开辟的,那free函数的行为是未定义的
2、如果参数memblock是NULL,则函数什么都不用做
如果不使用free,就可能会造成内存泄漏
关于内存泄漏,不深入去说了,但可以观察一个现象,看如下代码:
#include#include int main() { while (1) { malloc(10); } return 0; }
有兴趣的可以自己去试试。但是,轻易别尝试,对电脑不好,本博主的电脑就是准备试一下给你们截个图看看,电脑它不争气,卡死重启了,真的会难过。
如果有小伙伴想尝试,可以去运行,不关闭程序,然后去电脑的任务管理器中去查看内存,会发现内存会越来越少,最后的结果就是,电脑就卡死了。
有人会说,程序结束他自己不就释放了吗?再说了,上面的malloc的使用举例中,不就是没有使用free,也没有发现电脑有卡死的现象啊!
兄弟,想的很对,但是,有没有想过,刚才的程序运行很快结束了,万一我们有一个很大的项目,它运行起来,可能需要每天24小时不间断的运行,或者是想上述一样是个死循环,这时候是不是就有问题了,申请的内存空间就得不到释放,就很可能会造成内存泄露的,所以我们需要养成习惯,记得自己去释放它。
所以在上述代码在结尾加上free
问题又来了,我们将它free了之后,将相应的空间还给了操作系统,但是我们通过监视中观察到以下情况:
free之前的其存放的值:
继续向下看,free之后的值
看似是没有问题了,但是再看:
有没有发现p所指向的空间编号并没有发生改变,也就是说通过
我们还可以通过p找到那块已经不属于我们的空间,就是一个野指针了,关于野指针,可以看一看我的另一期,讲解更加详细。http://t.csdn.cn/HICut
所以我们不仅要free还要将其置为NULL.
3、calloc
用来动态内存分配空间
1、函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
2、与函数malloc的 区 别是,calloc会返回地址之前把申请的空间的每个字节初始化为全0
使用:
#includeint main() { //malloc(10*sizeof(int));//效率高,但不初始化 int* p=(int*)calloc(10, sizeof(int));//效率低一点点,但会自动初始化 if (p == NULL) { printf("开辟失败"); } else { int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i));//0 0 0 0 0 0 0 0 0 0 } } //释放空间 free(p); p = NULL; return 0; }
4、realloc
调整动态开辟内存空间的大小
realloc使用的注意事项:
1、如果p指向的空间之后有足够的内存空间可以追加,则直接追加,后返回p
2、如果p指向的空间之后没有足够的内存空间可以追加,则realloc会重新找一块内存区域,开辟一块满足需求的空间,并且把原来内存中的数据拷贝过来,释放旧的内存空间,最后返回新开辟的内存空间的地址
3、得用一个新的变量接收realloc函数的返回值
同时呢,为了防止内存空间不足,开辟失败,所以。利用realloc开辟空间的形式为:
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("strerror(errno");
return;
}
int* ptr = realloc(p, INT_MAX);
if (ptr != NULL)
{
p = ptr;
}
//释放内存
free(p);
p = NULL;
使用realloc要先将其开辟的空间赋给ptr,开辟失败,ptr为空,但是并不会影响原来的p,否则,开辟失败,直接赋给p,不仅没有调整成功,反而还把原来的p也变成NULL了。
如果开辟成功,再将ptr赋给p。
三、常见的动态内存错误
1、对NULL指针的解引用操作
#include
#include
int main()
{
int* p = (int*)malloc(INT_MAX+1);
//如果p的值为NULL,就会有问题
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;//对非法地址进行了解引用
}
free(p);
p = NULL;
free(p);
}
#include#include int main() { int* p = (int*)malloc(INT_MAX+1); //如果p的值为NULL,就会有问题 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i;//对非法地址进行了解引用 } free(p); p = NULL; free(p); }
INT_MAX+1开辟的空间太大了,就会开辟失败,返回一个NULL,而我们又没有去判断,直接拿来用,就会出现非法访问了。运行起来,就会出错。
2、对动态开辟的内存的越界访问
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
if (p == NULL)
{
return 0;
}
else
{
int i = 0;
for (i = 0; i < 10; i++)//只开辟了5个,却用到了10个
{
*(p + i) = i;
}
}
free(p);
p = NULL;
}
越界访问,代码运行时,会出现下列提示:
3、对非动态开辟内存使用free释放
int main()
{
int a = 10;
int* p = &a;
*p = 20;
free(p);
p = NULL;
return 0;
}
出错:
4、使用free释放动态开辟内存的一部分
int main()
{
int* p = (int*)malloc(40);//40个字节
if (p == NULL)
{
return 0;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*p++ = i;
}
free(p);
p = NULL;
return 0;
}
代码崩溃了,一直给p++,最后释放的是p后面的空间,而我们要是放的是最初的p的后面的空间
p变化,p所指的就不是malloc开辟的起始位置了
5、对同一块动态内存多次释放
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return 0;
}
//使用
//释放
free(p);
//.....
free(p);
return 0;
}
如何避免这个情况:
每次free(p);结束将其置为NULL
以上就是动态内存管理的主要内容了,使用及其容易出错的问题,我们下期见!!!



