目录
一、为什么存在动态内存分配
动态内存分配,顾名思义,就是可以对开辟的空间进行大小上的调整。
我们已经掌握的内存分配方式有如下两种
int a = 10;//在栈区开辟4字节的整型空间
char arr[10] = {0};//在栈区开辟10字节的连续的空间
那么为什么还要进行动态内存分配呢?
原因是以上开辟的内存空间都是固定的,有可能无法达到需求。比如,在实现一个通讯录系统中,利用数组开辟100块空间来存放联系人,如果用户只需要存3个人信息,那么剩下的97块内存空间就会大大浪费,如果用户需要存储多余100人的信息,那么内存空间就会不够,这带来极大的不便。动态内存开辟极好的解决了这一问题,根据我们的需求开辟空间。
二、动态内存函数的介绍
1.malloc
malloc函数会向内存(堆区)申请一块连续可用的空间,返回指向这块空间的指针。
使用malloc函数需注意几点
1.如果开辟成功 ,会返回指向这块空间的指针
2.如果开辟失败,会返回空指针(NULL),因此开辟后要检查是否开辟成功
3.该函数的返回值是void*,具体类型需要使用者根据实际情况自己决定
4.如果出现参数为0的情况,malloc的行为是标准未定义的,取决于编译器
非常重要的一点是
使用malloc函数向堆区申请了空间后,用完了要用free这个函数释放,否则会发生内存泄露。
下面举例说明
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
//int a = 10;//4个字节
//int arr[10];//40个字节
//这两种方式空间开辟的大小有自身的局限性
//动态内存开辟
int* p = (int*)malloc(40);
//判断内存空间是否开辟成功
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;//返回其它数字不正常返回
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//打印
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//没有free
//并不是说内存空间就不回收了
// 当程序退出的时候,系统会自动回收内存空间的
//内存泄露
//一块内存在堆区被开辟,在程序运行时一直没有释放,那么这块空间只能被占有
//别人是用不了,在一个程序中,如果一直开辟开辟空间不释放,那么内存会被填满
free(p);//释放开辟的空间
p = NULL;//如果p不置空,p仍然记得原来的地址,还是可以找到那块地址
//但那块地址已经没有内容了,防止p成为野指针,所以必须将p置空
return 0;//返回0表示正常返回
}
2.calloc
与malloc功能类似的还有calloc,其中,与malloc不同的地方是,calloc在开辟空间的同时还会
将这块内存空间全部初始化。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
//calloc的用途与malloc基本一致
int* p = (int*)calloc(10, sizeof(int));
//calloc函数在开辟空间的时候会把内容初始化成0
//这是calloc函数的固定功能
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
int i = 0;
//for (i = 0; i < 10; i++)
//{
// *(p + i) = i;
//}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
3.realloc
动态内存的动态就表现在reallloc这个函数上。
有时候我们会发现申请的空间太小了,或者申请的空间太大了,这时我们可以利用realloc函数对内存进行合理的调整。
realloc函数在调整内存空间时会出现两种情况
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
情况1
当是情况1 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址 。情况2
当是情况2 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
所以我们使用realloc函数时需要注意一些。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i + 1;
}
//扩容
int* ptr = (int*)realloc(p, 80);
//realloc函数不能直接用p来接收 why?
//开辟空间太大时,realloc函数开辟空间可能失败
//开辟空间失败后返回NULL指针,直接用p来接受,会让p忘记之前的地址
if (ptr != NULL)
{
p = ptr;
}
//使用
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
三、常见的动态内存的错误
1.对NULL指针进行解引用操作
以上解引用操作时是错误的,会被警告。
2.对动态内存开辟的空间越界访问
//2.对动态开辟的空间越界访问
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
int i = 0;
for (i = 0; i <= 10; i++)//越界访问
{
p[i] = i;
}
free(p);
p = NULL;
return 0;
}
这种非法操作会出现以上情况。
3.对非动态内存开辟的空间使用free释放
//3.对非动态开辟的内存使用free释放
int main()
{
int a = 10;
int* p = &a;
//......
free(p);
p = NULL;
return 0;
}
注:头文件没有添加
4.使用free释放动态开辟内存的一部分
//4.使用free释放动态内存开辟的一部分
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*p = i;
p++;
}
//释放
free(p);
p = NULL;
return 0;
}
注:头文件没有添加
5.对同一块动态内存多次释放
//5.对同一块动态内存多次释放
int main()
{
int* p = (int*)malloc(40);
//...
free(p);
p=NULL;
//...
free(p);
return 0;
}
注:头文件没有添加
6.动态开辟的内存未释放(内存泄漏)
//6.动态内存开辟忘记释放(内存泄露)
void test()
{
int* p = (int*)malloc(100);
//...
//有可能有些人这么写代码
int flag = 0;
scanf("%d", &flag);
if (flag == 5)
return;
//如果返回以下代码没有机会执行
//开辟的内存空间在程序执行的时候再也找不到了
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
注:头文件没有添加