当前位置: 首页 > news >正文

深入理解动态内存管理(C语言)

深入理解动态内存管理(C语言)


为什么有动态内存管理


int a = 0;
int val[10];

在学习动态内存管理之前,我们开辟的空间大小往往是固定的
即使开辟一个数组,也需要在创建的时候确定长度,并且大小无法修改

然而我们在开辟空间时,可能会遇到上述之外的情况,比如在程序运行时,才知道需要的空间大小

这时候有人说,可以开辟一个很大的空间

但是这个很大的空间也是有大小上限的,而且有时候还会造成空间的过度浪费

于是C语言引入了动态内存开辟,让程序员可以灵活地申请和释放空间

C语言在进行动态内存管理的,会涉及四个函数


mallocfree


malloc

首先我们来看第一个函数malloc,函数原型如下:

void* malloc(size_t size)

功能:向内存中的堆区申请一块size大小连续可用的空间,并且返回这块空间的起始地址

  • 如果开辟成功,则返回这块空间的起始地址
  • 如果开辟失败,则返回NULL,因此开辟完成后需要检查
  • 返回的指针类型是void*,是为了让使用者自己决定空间类型
  • 参数size单位是字节,如果为0,malloc的行为是标准未定义的,取决于编译器
    现在,我们可以尝试使用malloc
#include <stdio.h>
#include <stdlib.h>//动态内存管理函数相关的头文件int main()
{//假设向内申请20个byte,存放5个整数int* p = (int*)malloc(5 * sizeof(int));//判断返回值if (p == NULL)//开辟失败,打印错误信息{perror("malloc");//malloc :.....return 1;}//开辟成功,使用空间for (int i = 0; i < 5; i++){*(p + i) = i + 1;}for (int i = 0; i < 5; i++){printf("%d ", p[i]);}return 0;}

在这里插入图片描述
ok,这里成功开辟了

当然,有时候也会有开辟失败的时候,例如开辟的空间太大
这里我们把这段代码

int* p = (int*)malloc(5 * sizeof(int));

改成这样

int* p = (int*)malloc(INT_MAX * sizeof(int));

在这里插入图片描述

在x86环境下,就可以看到,开辟失败了,并且打印的错误信息是:没有足够空间

所以,我们在使用malloc去申请空间的时候,一定要去检查返回值


free


有借有还,再借不难

我们在向内存申请完空间之后,还需要主动去释放归还所申请的空间
否则这块空间就会在程序运行期间,一直占用

我们的free函数就是用来释放动态申请的空间的

void free(void* ptr)

功能:释放和回收动态开辟的内存

  • 如果参数ptr指向的空间不是动态开辟的,那free函数行为是未定义的
  • 如果ptrNULL,则函数什么都不做

所以对于上面使用malloc的代码,我们需要对它申请的空间进行释放

#include <stdio.h>
#include <stdlib.h>//malloc和free
int main()
{//假设向内申请20个byte,存放5个整数int* p = (int*)malloc(5* sizeof(int));//判断返回值if (p == NULL)//开辟打印,错误信息{perror("malloc");//malloc :.....return 1;}//开辟成功,使用空间...//不再使用这块空间,我们要把它回收释放、free(p);//释放这块空间return 0;
}

但是这样就好了吗?
我们来调试一下,看看释放空间后指针p的情况
在这里插入图片描述
上面是我们释放完空间之后,指针p指向的内存空间情况

可以看到,虽然已经对这块空间进行释放了,但是这块空间并没有置为NULL,这就导致了p成为野指针

所以,我们在使用完free释放掉内存之后,还需要手动把指针置空

#include <stdio.h>
#include <stdlib.h>//malloc和free
int main()
{//假设向内申请20个byte,存放5个整数int* p = (int*)malloc(5* sizeof(int));//判断返回值if (p == NULL)//开辟打印,错误信息{perror("malloc");//malloc :.....return 1;}//开辟成功,使用空间...//不再使用这块空间,我们要把它回收释放、free(p);//释放这块空间,但是不会将p置空p = NULL;//置空return 0;
}

上面就是我们动态内存管理中,很常见的一对函数


callocrealloc


除了上面两个函数之外,C语言中还有两个用来动态内存分配的函数``


calloc

我们首先来看calloc函数

void* calloc(size_t num,size_t size)

功能:在堆区开辟num个大小为size(单位字节)的空间,并且把空间的每个字节初始化为0

  • malloc的区别只在于参数拆开了和开辟空间的初始化了

我们可以直接打印开辟好的空间来看看

#include <stdio.h>
#include <stdlib.h>
//malloc
int main()
{//申请10个整型的空间int* p = (int*)malloc(10 * sizeof(int));//判断返回值if (p == NULL){perror("calloc");return 1;}for (int i = 0; i < 10; i++){printf("%d\n", p[i]);}free(p);//释放空间p = NULL;//置空return 0;
}

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
//calloc
int main()
{//申请10个整型的空间int* p = (int*)calloc(10, sizeof(int));//判断返回值if (p == NULL){perror("calloc");return 1;}for (int i = 0; i < 10; i++){printf("%d\n", p[i]);}free(p);//释放空间p = NULL;//置空return 0;
}

在这里插入图片描述
可以看到malloc开辟的空间没有初始化
calloc初始化了为0


realloc

有时候,我们动态开辟的空间可能会大了或者小了,这时候我们就可以使用realloc函数对动态内存进行大小的调整,让动态内存管理变得更加灵活


函数原型:

void* realloc(void* ptr,size_t size)

功能:在堆区把ptr指向的动态开辟的空间大小,调整为size(单位字节),并且返回调整后空间的地址
我们来模拟使用一下:

#include <stdio.h>
#include <stdlib.h>
//realloc
int main()
{//申请一块空间存放1-5int *p = (int*)calloc(5, sizeof(int));//判断返回值if (p == NULL){perror("calloc");return 1;}for (int i = 0; i < 5; i++){p[i] = i + 1;}//想扩大,继续放6-10p = (int*)realloc(p, 40);for (int i = 5; i < 10; i++){p[i] = i + 1;}
//释放置空free(p);p = NULL;return 0;
}

我们来看这段代码

它用原来的指针p,来接收了新开辟的空间,但是注意,我们realloc在开辟空间的时候,也有可能会开辟失败,一旦失败,p为NULL,对它解引用会报错,并且原来的数据也会丢失

所以,我们一般会再创建一个指针变量来接收新地址,确认成功开辟后再把这个地址给p

//想扩大,继续放6-10
int *p2 = (int*)realloc(p, 40);
//判断返回值
if (p2 == NULL)
{//业务处理
}
//增加成功
p = p2;

我们对这段代码进行调试
在这里插入图片描述
可以看到,其实realloc动态开辟后的空间和原来p的空间起始地址不一样,不是原本的那块空间

其实这是因为realloc在调整空间的时候会遇到两种情况:
①原有空间之后有足够的空间
②原有空间之后没有足够空间

对于情况①,要扩展空间,只需要在原来内存之后增加空间,原数据不会动

但对于情况②,会先在堆区找一块大小合适的空间,把原有数据拷贝过来,再释放原空间,之后返回新空间地址

由于存在上述两种情况,所以我们在使用realloc的时候,需要注意一下

其实我们的realloc函数还可以直接用来动态开辟空间

int *p = (int*)realloc(NULL,40

当我们第一个参数传NULL时,realloc就会直接开辟size字节大小的空间,效果和malloc一样


  • 上述函数,均包含在stdlib.h头文件中

动态内存管理中常见的错误


通过上面的介绍,可以发现,我们在进行动态内存管理的时候,其实是有很多需要注意的地方的,于是我总结了六个比较常见错误,希望可以帮助到大家


①对NULL指针的解引用

void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}

对于动态申请的空间,需要先判断返回值再使用

②对动态开辟空间的越界访问

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;//当i是10的时候越界访问
}
free(p);
}

③对非动态开辟空间使用free来释放

void test() 
{
int a = 10;
int *p = &a;
free(p)
}

④使用free去释放动态开辟空间的一部分空间

void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}

⑤对同一块动态内存多次释放

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

⑥动态开辟内存忘记释放(内存泄漏)

void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
return 0;
}

一些建议

  • 谁申请的空间,谁释放
  • 不使用的空间,及时释放
  • 自己(函数1)不方便释放,要告诉别人(函数2)释放
  • mallocfree一般成对出现

总结

以上就是我们动态内存管理相关的内容了,如果有什么问题,可以评论留言,感谢支持

http://www.dtcms.com/a/474525.html

相关文章:

  • Viterbi解码算法:从理论到实践
  • 怎么在网站做推广不要钱珠海微信网站开发
  • 【文件快速搜索工具】实用工具强推之Everything-快速搜索工具的详细图文下载安装教程
  • sql优化之索引下推误区
  • 编程基础:组件编程思想
  • 小兔鲜项目要点总结
  • 检测网站速度广州免费停车的地方
  • 【C++】list相关接口及模拟实现
  • Vue-MVVM 模型
  • 网站需要什么费用高端品牌网站有哪些
  • Emacs折腾日记(三十二)——org mode的基本美化
  • 从数据混沌到智能驱动:非结构化数据中台的技术实践与方法论指南
  • 什么是自相关分析(ACF)?
  • Web前端开发,新手入门指南
  • 织梦增加网站英文名称百度商桥怎么和网站
  • Paper2Agent:将科研论文转化为可交互的AI智能体工具项目
  • 静态网页 vs 动态网页:爬虫该如何选择抓取策略?
  • AI/CICD/Next/React NativeTaro内容
  • godot 通过 GDExtension 配置 C++ 开发环境
  • XMLHttpRequest对象
  • 广州市外贸网站建设内容管理系统开发
  • 带你了解STM32:SPI通信(软件部分)
  • 标量子查询优化(二)
  • 网站建设的客户都在哪里Wordpress西联
  • ppo dino 多余尺寸删除ai 思路2 绕过cad软件
  • 【LeetCode】66. 加一
  • 日志1--时间戳类型设计
  • 手机网站 qq代码免费app制作工具
  • MyBatis-Plus 全方位使用指南:从基础 CRUD 到复杂查询
  • avalonia的hello示例及mvvm实现