今天学了动态内存管理的相关知识点,首先什么是动态内存呢,我的理解是可大可小的,能够动态变化的。
1. 为什么存在动态内存分配 我们已经掌握的内存开辟方式有:int main()
{
int a = 10;
int arr[10] = { 0 };
int n;
scanf("%d", &n);
int arr1[n];
return 0;
}
向上面这些,例如固定写好10个数组,或者利用变长数组的来在栈区开辟空间,但是这种空间大小是固定,就很死板。因此我们可以学习动态内存开辟的方式。而学习动态内存开辟最主要就是学习动态内存开辟的函数。malloc ralloc free跟cealloc这些开辟内存的函数。
2. 动态内存函数的介绍 2.1 malloc 和 free C 语言提供了一个动态内存开辟的函数:void* malloc (size_t size);
这个函数向内存申请一块
连续可用
的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个
NULL
指针,因此
malloc
的返回值一定要做检查。
返回值的类型是
void*
,所以
malloc
函数并不知道开辟空间的类型,具体在使用的时候使用者自己
来决定。
如果参数
size
为
0
,
malloc
的行为是标准是未定义的,取决于编译器
C
语言提供了另外一个函数
free
,专门是用来做动态内存的释放和回收的,函数原型如下
void free (void* ptr);
free
函数用来释放动态开辟的内存。
如果参数
ptr
指向的空间不是动态开辟的,那
free
函数的行为是未定义的。
如果参数
ptr
是
NULL
指针,则函数什么事都不做。
int main()
{
int* p = (int*)malloc(100);//强制类型转化成整型指针,返回动态开辟内存的起始地址
if (p == NULL)//判断指针是否为空
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 25; i++)
{
*(i + p) = i + 1;
}
for (i = 0; i < 25; i++)
{
printf("%d ", *(i + p));
}
free(p);//释放空指针
p = NULL;//将开闭的起始地址初始化为0
return 0;
}
2.2
calloc
C
语言还提供了一个函数叫
calloc
,
calloc
函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
函数的功能是为
num
个大小为
size
的元素开辟一块空间,并且把空间的每个字节初始化为
0
。
与函数
malloc
的区别只在于
calloc
会在返回地址之前把申请的空间的每个字节初始化为全
0
。
int main()
{
int*p=(int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for(i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
跟刚刚的malloc非常相似。这些开辟内存的函数要跟free搭配使用,使用完要把起始指针初始化为NULL。
所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用 calloc 函数来完成任务。 2.3 realloc realloc 函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。 函数原型如下void* realloc (void* ptr, size_t size);
ptr
是要调整的内存地址
size
调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到
新
的空间。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p=(int*)malloc(100);
if (p == NULL)
{
perror("malloc");
return 1;
}
int* pr = (int*)realloc(p, 200);//将内存扩大能放50个元素
if (pr != NULL)
{
p = pr;
}
int i = 0;
for (i = 0; i < 50; i++)
{
*(p + i) = i + 1;
}
for (i = 0; i < 50; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
realloc函数调整动态内存大小的时候会有俩种情况:
情况1:原有空间之后有足够大的空间
情况 2 :原有空间之后没有足够大的空间 情况 1 当是情况 1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。 情况 2 当是情况 2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。由于上述的两种情况,realloc 的使用就要注意一些 此时,需扩展的空间后方没有足够的空间可供扩展,并且堆区中也没有符合需要开辟的内存大小的空间。结果就是开辟内存失败,返回一个NULL。我们再接收扩容的起始地址的时候就需要新建一个函数指针来接收,扩容的空间是失败返回NULL. 常见的动态内存错误 一对 NULL 指针的解引用操作 如果malloc开辟空间失败,返回空指针,然后对返回的指针进行解引用操作就会导致非法访问void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
二对动态开辟空间的越界访问
如果malloc开辟10个元素的空间,你偏偏要放11个元素进去,就会导致越界访问。
int main()
{
int* p = (int*)malloc(40);
if (p != NULL)
{
int i = 0;
for (i = 0; i <= 10; i++)
{
*(p + i) = i + 1;
}
}
return 0;
}
三对非动态开辟的内存使用free释放
free函数只能释放动态开辟的内存空间。
int main()
{
int*p=20;
free(P);
p=NULL;
return 0;
}
四、使用free释放动态开辟内存的一部分
free函数只能从开辟好的动态内存空间的起始位置开始释放,所以使用free函数释放动态内存时,传入的指针必须是当时开辟内存时返回的指针。
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
五
对同一块动态内存多次释放
、对同一块内存多次释放对同一块动态内存空间只能释放一次。避免这个问题的出现也很简单,我们只要记住在第一次释放完空间后立即将该指针置为NULL即可,因为当传入free函数的指针为NULL指针时,free函数什么也不做(也就不会出现对同一内存多次释放的问题)。
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
六动态开辟内存忘记释放(内存泄漏)
一定要做到自己开辟的动态内存自己记得释放,也许你觉得这件事没什么,但当你需要从几十万甚至几百万行代码中找出一个因忘记释放动态内存而造成的内存泄漏问题时,你就会真正知道这件事情要性。