本篇文章是对动态内存分配部分内容的学习分享,包含了四个内存函数的接受奥与使用以及常见的一些错误
那咱们废话不多说,直接开始吧!
1. 动态内存功能存在的意义
说到内存开辟也许我们并不能马上做出反应且清楚得知道指的是什么
事实上,我们已经掌握了内存开辟方法了只是我们对这个概念还不是很清晰:
int a = 10;
int b[10] = {0};
char c = 'A';
......
以上都是内存开辟的形式,只要我们创建了变量,实际上也就是开辟了内存,因为我们需要这部分内存来存放创建的变量值。
但上述的内存开辟方法有两个特点:
- 空间开辟⼤⼩是固定的
- 数组在申明的时候必须指定其⻓度,⼀旦确定了⼤⼩便不能调整
如此一看,感觉这种内存开辟的方法的确是缺少了一点灵活的感觉,并且有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,那上述数组的编译时开辟空间的⽅式就无法满⾜我们的需求了。
于是C语⾔引⼊了动态内存开辟,这样程序员就可以自行申请和释放空间,灵活度相对也就提高了不少。
2. 内存管理函数
在开始讲内存函数前我们需要知晓它们都被包含在了头文件stdlib.h中
2.1 malloc
在cplusplus.com上我们能够大体了解到这个函数的一些基本属性:
(点击这个链接cplusplus.com/reference/cstdlib/malloc/跳转网页)
2.1.1 函数形式
void* malloc (size_t size);
这是一个void*类型的函数,也就是说它会返回任意的一个指针类型
后面的参数则是需要开辟的内存空间(单位:字节)
2.1.2 函数的特性与使用
这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。 - 如果开辟成功,则返回⼀个指向开辟好空间的指针。 - 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。 - 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃ ⼰来决定。 - 如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器 在了解完了函数所具有的一些特性之后,我们便可以开始试着用一下malloc函数了:#include<stdio.h>
#include<stdlib.h>
int main()
{
int n=0;
scanf("%d",&n);
//分配n个整型大小的内存
int * p=(int *)malloc(n * sizeof(int));
//判断内存是否分配成功
if (p == NULL)
{
perror("malloc");
return 1;
}
else
printf("内存分配成功!");
return 0;
}
运行效果:
分配成功
分配失败
注意:
因为malloc是void*类型的函数,因此在使用的时候需要视情况而定要将其强制类型转换成什么类型,像我这里是用的 int *p 指针接收的,那么就需要将函数转成 int * 类型
2,2 free
对于这个函数,cplusplus.com上是这么说的(请忽略“自由”、“无空”两处无脑翻译...):
(点击链接cplusplus.com/reference/cstdlib/free/跳转网页)
2.2.1 函数形式
void free (void* ptr);
C语⾔提供了另外⼀个函数free,是专门⽤来做动态内存的释放和回收的,这也就解释了为什么后面的参数是void*类型,因为它需要接收指向不同类型的内存的指针。
2.2.2 函数的特性与使用
free函数⽤来释放动态开辟的内存。 - 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。 - 如果参数 ptr 是NULL指针,则函数什么事都不做。#include <stdio.h>
#include <stdlib.h>
int main()
{
int num = 0;
scanf("%d", &num);
int arr[num] = {0};
int* ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));
printf("%p\n", ptr);
if(NULL != ptr)//判断ptr指针是否为空
{
int i = 0;
for(i=0; i<num; i++)
{
*(ptr+i) = 0;
}
}
free(ptr);//释放ptr所指向的动态内存
printf("%p\n", ptr);
ptr = NULL;//是否有必要?
printf("%p\n", ptr);
return 0;
}
上面这段代码便是malloc与free的用法,但内存都已经被释放了后面的ptr=NULL还有必要写吗?
我们分别在刚给ptr分配完内存、free函数使用后、ptr=NULL后三个地方都加上个打印地址代码
运行一下观察效果:
能够发现,在ptr=NULL执行前,ptr一直都指向那个内存
就好像是和ta分手了,但是手机里还一直存着它ta的手机号码,但你再打过去ta也不会再接了...
既然如此还有什么必要继续留着呢?(今天,你痛了吗...)
因此,在最后加上个指针初始化操作还是很有必要的。
2.3 calloc
对于这个函数,cplusplus上是这么说的:
(点击链接cplusplus.com/reference/cstdlib/calloc/跳转网页)
2.3.1 函数形式
void* calloc (size_t num, size_t size);
C语⾔还提供了⼀个函数叫
calloc
, calloc 函数也⽤来动态内存分配
与malloc函数一样的void*类型,注定了需要我们为它做出强制类型转换的操作
后面的两个参数,第一个是要开辟的数量num,第二个是指开辟一个大小为size的空间
2.3.2 函数的特性与使用
函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
}
else
{
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
free(p);
p = NULL;
return 0;
}
用calloc函数开辟了10个大小为int的内存,运行观察结果:
的确是将内存初始化为0了
所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使⽤calloc函数来完成任务!2.4 realloc
这第四个内存函数,cplusplus.com上是这么说的:
(点击链接cplusplus.com/reference/cstdlib/realloc/跳转网页)
2.4.1 函数形式
void* realloc (void* ptr, size_t size);
返回值是
调整之后的内存起始位置
参数部分,第一个是一个指向需要修改内存大小的地址,第二个是需要的新的内存大小
2.4.2 函数的特性与使用
有时会我们发现过去申请的空间太⼩或太大了,为了合理且方便地使⽤与修改内存,realloc的出现就显得无比恰当。也正是它的出现让动态内存管理更加灵活。 这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。 realloc在调整内存空间的是存在两种情况: - 情况1:原有空间之后有⾜够⼤的空间 - 情况二 : 原有空间之后没有⾜够多的空间 情况三:内存修改失败 函数将会返回 NULL,因此为了原来的内存被NULL覆盖掉,我们需要创建一个新的指针去接收,若是检测分配成功,在将新的地址赋给旧的指针 接下来我们一起来看一段代码:#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)calloc(10,sizeof(int));
printf("开辟的内存地址:%p\n", p);
if (p == NULL)
{
perror("calloc");
return 1;
}
int* ptr = realloc(p, 80);
if (ptr == NULL)
{
perror("realloc");
return 1;
}
else
{
p = ptr;
printf("新的内存地址为:%p \n", p);
}
//free(p);
free(ptr);
//p = NULL;
ptr = NULL;
return 0;
}
我们先用calloc函数开辟一块内存空间,再用realloc扩大这块空间
将刚开辟好的、扩大之后的内存空间地址打印出来观察结果:
不难看出,系统为我们找了一块新的空间开辟目标大小的内存,使得两次地址发生了变化
这时候问题来了:
先后创建的两个指针 p 与 ptr 都要free()掉吗?
答案是:不能!坚决不能!这样会造成内存重复释放错误!
观察代码我们能够知道新的内存有一个新的指针ptr来指向,在确定没问题后才赋给了p指针,此时两个指针指向的是同一块内存空间,无论我们free哪一个,这块空间都已经被释放了,此时我们再free另外一个指针便会导致内存重复释放错误
(像这样...)
但将两个指针都赋为NULL的操作是可以一起进行的~
3. 常见的动态内存错误
3.1 对NULL指针的错误引用
int main()
{
int n=0;
scanf("%d ",&n);
int * p=(int *)malloc(n*sizeod(int));
*p = 20;
free(p);
}
如果分配内存失败了变回返回NULL给到指针p,此时再对p进行解引用的操作就会出错。
因此,我们需要及时对p进行检查
int main()
{
int n=0;
scanf("%d ",&n);
int * p=(int *)malloc(n*sizeod(int));
//加上判断
if(NULL == p)
{
perror("malloc");
return 1;
}
*p = 20;
free(p);
}
3.2 对动态开辟空间的越界访问
int* p1 = (int*)malloc(40);
if (p1 == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
* (p1+i) = i;
}
for (int i = 0; i <= 10; i++)//循环了11次,越界访问
{
printf("%d ", *(p1 + i));
}
free(p1);
p1 = NULL;
在for循环中循环次数多了一次,造成了越界访问
这种注意一下下标一循环次数就可以了,问题不大
3.3 对非动态开辟内存使用free释放
int* p2 = (int*)malloc(40);
int arr[10] = { 0 };
p2 = arr;
free(p2);
这里将非动态内存的arr地址给p2,再对p2进行释放内存操作就会发生错误
3.4 使⽤free释放⼀块动态开辟内存的⼀部分
int* p3 = (int*)malloc(40);
int i = 0;
for (i = 0; i < 10; i++)
{
*p3 = i;
p3++;
}
//此时的p3所指向的位置已经不是这块内存的开头位置,便无法正常释放这块内存
free(p3);
此时的p3的位置是不对劲的
此时再去释放内存便会出错
因此我们一般需要在创建一个指针当做可移动的赋值指针,另一个则用来销毁:
int* p3 = (int*)malloc(40);
//再创建一个指针p4
p4=p3;
int i = 0;
for (i = 0; i < 10; i++)
{
*p4 = i;
p4++;
}
//这样就可以正常释放内存了
free(p3);
3.5 对同⼀块动态内存多次释放
这部分具体见上面的realloc部分
3.6 动态开辟内存忘记释放(内存泄漏)
这个部分十分容易被忽略,只要我们不小心忘记了释放分配好的空间便会出现这个问题
在主函数中:
int* p4 = (int*)malloc(40);
while (1);
当然也有可能出现在封装的函数中:
void test()
{
int* p = (int*)malloc(100);
if (p != NULL)
{
*p = 1;
}
}
int main()
{
test();
while (1);
return 0;
}
也许你会想那我们直接在主函数中对p进行释放不就可以了
事实上,p仅仅是函数中的一个局部变量,等我们在主函数中再想去找p指向的那块地址时已经找不到了
因此,我们需要在函数中便将p释放
void test()
{
int* p = (int*)malloc(100);
if (p != NULL)
{
*p = 1;
}
free(p);
p=NULL;
}
int main()
{
test();
while (1);
return 0;
}
或者,将函数返回值类型改为指针类型,再在主函数中创建一个指针去接收它,然后就可以在主函数中释放了
int * test()
{
int* p = (int*)malloc(100);
if (p != NULL)
{
*p = 1;
}
return p;
}
int main()
{
int * p2=test();
p2=NULL;
return 0;
}
以上便是本次内容分享的所有内容了,感谢你能看到这里,希望本次的分享也能够对你有所帮助
如果可以的话也麻烦来个三连,对我来说将会是莫大的鼓舞~
那就让我们,下次再见!
标签:malloc,函数,int,free,C语言,内存,动态内存,NULL,分配 From: https://blog.csdn.net/2301_80029060/article/details/142566435