【c语言】详解动态内存管理

news2024/11/26 3:55:47

目录

  • 关于动态内存分配
  • malloc和calloc函数介绍
  • 动态内存回收----free
  • realloc函数介绍
  • 常见的动态内存错误

关于动态内存分配

回想一下我们之前学过的内存开辟方式:

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

在学习c语言时我们知道数据结构通常是固定大小的。就拿数组举例,一旦程序完成编译,那么数组的大小及元素的个数就确定了。那么在不修改程序并且再次编译程序的情况下就无法改变数据结构的大小。总结就是下面两个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,数组空间⼀旦确定了大小不能调整。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
于是乎C语言便引入了动态内存开辟,即让程序员自己可以申请和释放空间,下面将对如何动态开辟内存进行介绍

malloc和calloc函数介绍

下面是cplusplus对malloc的定义:

void* malloc (size_t size);

这个函数向内存申请一块连续可以的空间,并返回指向这块空间的指针。size即是想要申请内存空间的大小,void*即是指向该申请内存的首元素地址,因为不知道类型,所以用void*,还有以下注意点:

  • 如果开辟成功,则返回⼀个指向开辟好空间的指针
  • 如果开辟失败,则返回⼀个NULL指针,因此malloc的返回值⼀定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc 的行为是标准是未定义的,取决于编译器。

再来看一下cplusplus对calloc的定义:

void* calloc (size_t num, size_t size);

其实malloccalloc是极其相似的,ralloc中参数size是想要申请的数据类型的每个的大小,而num就是想要申请的数据类型的个数,申请的总大小就为num*size,其实就可以用malloc中的size表示。其余特点也和malloc相似,这就不多介绍了。
当然这两者也存在区别,如下:

  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为0.

所以如果我们想要对动态申请的内存空间初始化为0,那么使用ralloc就更方便。

动态内存回收----free

其实malloccalloc等动态开辟内存的函数,实则是在堆区上开辟内存。由于这些函数申请的内存空间系统并不会主动回收,所以过于频繁的使用这类函数开辟空间,就会导致堆耗尽。这时就需要我们主动释放开辟的空间,于是乎引入free函数,函数原型如下:

void free (void* ptr);

关于这里的ptr指针,则是指向我们动态开辟的内存的首地址,只有指向首地址才能完全释放动态开辟的内存空间。关于ptr指针还有以下两个特殊情况;

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

还有两个注意事项

  1. 在我们释放开辟的空间后,原来指向这段空间的指针ptr还存着此处的地址,为了避免后面不小心对此指针进行赋值或解引用,导致野指针问题,所以在释放完空间后,还需将此指针赋为NULL
  2. 在写代码时最好始终有一个指向该空间的指针,如果没有指向该空间的指针,那么这段空间将无法访问和释放。对程序而言,不可访问的空间也被称为垃圾,留有垃圾的程序存在内存泄漏现象。
    在这里插入图片描述

如上图所示,原指针p指向第一个内存块,操作后p指向了第二个内存块。所以由于没有指针指向第一个内存块,就再也不能使用此内存块了,这就是上文所说的垃圾,导致了内存泄漏

realloc函数介绍

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的申请内存,我们⼀定会对内存的大小做灵活的调整。此时realloc函数就可以做到对动态开辟内存大小的调整
函数原型如下啊:

void* realloc (void* ptr, size_t size);

其中指针ptr指向的是要调整的内存地址,size是调整后的内存大小。返回值为调整后的内存的起始地址。
情况1:原有空间之后有足够大的空间
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
在这里插入图片描述

情况2:原有空间之后没有足够大的空间
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是⼀个新的内存地址。在此情况时,对于原先内存上已有的数据,此函数则会将那些数据拷贝到新的内存上而原内存将被释放
在这里插入图片描述
还有就是为了防止realloc开辟动态内存空间失败时,将指针赋为NULL所导致找不到原内存空间的问题。我们一般创建一个新指针来接收地址,判断不为NULL后再赋给原指针,如下:

int main()
{
     int* ptr=(int*)malloc(5*sizeof(int));
     int* p=(int*)realloc(10*sizeof(int));
     if(p != NULL)
          ptr = p;
     //......(代码)
     free(ptr);
     ptr = NULL;
     return 0;
}

常见的动态内存错误

  1. 对NULL指针的解引用操作:
 void test()
 {
      int *p = (int *)malloc(INT_MAX/4);
      *p = 20;
      free(p);
 }

这此代码中如果p的值是NULL,就会有问题。

  1. 对动态开辟空间的越界访问:
 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);
 }
  1. 对非动态开辟内存使用free释放:
void test()
{
     int a = 10;
     int *p = &a;
     free(p);
}

这里的a是在栈区上开辟的,如果用free释放系统将会报错。

  1. 对同⼀块动态内存多次释放:
void test()
 {
      int *p = (int *)malloc(100);
      free(p);
      free(p);//重复释放
 }

这里malloc开辟的动态内存空间,被重复释放,系统同样会报错。

  1. 使用free释放⼀块动态开辟内存的⼀部分:
 void test()
 {
      int *p = (int *)malloc(100);
      p++;
      free(p);
 }

因为++符号会改变变量的值,所以这里的p不再指向动态内存的起始位置,这时使用free释放时并不会释放完全部的动态内存。

  1. 动态开辟内存忘记释放(内存泄漏):
 void test()
 {
      int *p = (int *)malloc(100);
      if(NULL != p)
     {
          *p = 20;
     }
 }
 int main()
 {
      test();
      while(1);
 }

在调用完test()函数后没有主动释放开辟的内存空间,同样在栈区的int* p在调用完此函数后将被回收,所以就无法寻找到malloc开辟的空间,这就是上文所说的垃圾,而留有垃圾的程序存在内存泄漏现象。所以切记动态开辟的内存一定要释放!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1042813.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows系统ping命令的c++实现

// ping.cpp : 定义控制台应用程序的入口点。 //#include <winsock2.h> #include <stdio.h> #include <stdlib.h> #define ICMP_ECHO 8 #define ICMP_ECHOREPLY 0 /* The IP header */ typedef struct iphdr { unsigned char h_len:4; // length of the…

什么是内容运营?

关于内容运营&#xff0c;在不同种类的公司&#xff0c;侧重点也不一样。 电商平台的内容运营岗更偏内容营销&#xff1b;产品功能比较简单的公司&#xff0c;内容运营和新媒体运营的岗位职责差不多&#xff1b;而内容平台的内容运营更多的是做内容的管理和资源整合。

使用ElementUI结合Vue完善主页的导航菜单和书籍管理的后台数据分页查询

目录 一、动态树 ( 1 ) 数据表 ( 2 ) 后端 ( 2 ) 前端 二、书籍管理 数据表 后端 前端 ElementUI的背景 是一套基于Vue.js的桌面端组件库&#xff0c;由饿了么前端团队开发维护。它提供了丰富的UI组件和交互效果&#xff0c;可以帮助开发者快速构建出美观、易用的We…

第77步 时间序列建模实战:多因素预测 vol-2(以ARIMA为例)

基于WIN10的64位系统演示 一、写在前面 上一期&#xff0c;我们构建了多变量的ARIMA时间序列预测模型&#xff0c;其实人家有名字的&#xff0c;叫做ARIMAX模型&#xff08;X就代表解释变量&#xff09;。 这一期&#xff0c;我们介绍其他机器学习回归模型如何建立多变量的时…

『C语言进阶』qsort函数及模拟实现

&#x1f525;博客主页&#xff1a; 小羊失眠啦 &#x1f516;系列专栏&#xff1a; C语言 &#x1f325;️每日语录&#xff1a;没有退路&#xff0c;只能让自己变得强大 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前言 在上篇指针进阶中&#xff0c;我们对函数指针、函数…

深入理解 Swift 新并发模型中 Actor 的重入(Reentrancy)问题

问题现象 我们知道,Swift 5.5 引入的新并发模型极大简化了并行逻辑代码的开发,更重要的是:使用新并发模型中的 Actor 原语可以大大降低并发数据竞争的可能性。 不过,即便 Actor 有如此神奇之功效,它也不是“万能药”,仍不能防止误用带来的问题。比如:Actor 重入(Reen…

132.【MySQL_进阶篇】

MySQL_进阶 (一)、存储引擎1.MySQL体系结构(1).连接层(2).服务层(3).引擎层(4).存储层 2.存储引擎简介(1).查看某张表的数据引擎(2).展示此版本支持的所有存储引擎(3).创建表my_myisam,并指定MyIASM存储引擎(4).存储引擎示列 3.存储引擎 _ Innodb(1).Innodb 介绍(2).Innodb 特点…

通过 HelpLook ChatBot AI自动问答机器人降低客户服务成本

在当今竞争激烈的商业环境中&#xff0c;提供卓越的客户服务对于维持忠诚的客户群和推动业务增长至关重要。客户服务涵盖了公司与其客户之间的所有互动&#xff0c;包括解答问题、解决问题和提供支持。它在塑造客户对品牌的看法方面起着关键作用&#xff0c;并且可以显著影响他…

NSSSCTF做题(2)

1.[BJDCTF 2020]easy_md5 打开页面发现没什么东西&#xff0c;只有一个提交表单&#xff0c;然后url会显示你提交的信息 源代码里也看不到什么 &#xff0c;用dirsearch扫一下&#xff0c;这些是扫出来的数据 抓包看到了提示 hint: select * from admin where passwordmd5($pas…

【文件操作——详细讲解】

1. 为什么使用文件&#xff1f;&#x1f9d0; 如果没有⽂件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运⾏程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数…

cmd下scala退出命令

cmd下scala退出命令 &#xff1a;quit

checksec使用

checksec Relro&#xff1a;Full Relro&#xff08;重定位表只读&#xff09; Relocation Read Only&#xff0c; 重定位表只读。重定位表即.got 和 .plt个表。 Stack&#xff1a;No Canary found&#xff08;能栈溢出&#xff09; 栈保护。栈溢出保护是一种缓冲区溢出攻击缓解…

Docker 部署前端项目(非自动化)

部署前端项目 1. nginx配置文件1.1 nginxConf 2. 创建容器2.1 添加项目2.2 下载项目依赖2.3 打包前端项目2.4 创建容器2.5 查看容器 3. 视频演示4. 注意 1. nginx配置文件 1.1 nginxConf 首先你需要有nginx配置文件&#xff0c;你可以执行以下命令获取配置文件 # 安装镜像-生成…

JS进阶-闭包

概念&#xff1a;一个函数对周围状态的引用捆绑在一起&#xff0c;内层函数中访问到其外层函数的作用域 简单理解&#xff1a;闭包&#xff08;Closure&#xff09;内层函数外层函数的变量 function outer() {const a 1function f() {console.log(a)}f()} outer() 闭包作用…

最新商道融绿ESG数据(2015-2023)

数据简介&#xff1a;环境、社会和公司治理&#xff08;environmental, social, and corporate governance, 简称ESG&#xff09;方面的非财务信息已经普遍被用于评估企业在可持续发展方面的绩效。投资者通过考察上市公司的ESG绩效&#xff0c;便于识别企业面临的风险&#xff…

L1-033 出生年 c++解法

一、题目再现 以上是新浪微博中一奇葩贴&#xff1a;“我出生于1988年&#xff0c;直到25岁才遇到4个数字都不相同的年份。”也就是说&#xff0c;直到2013年才达到“4个数字都不相同”的要求。本题请你根据要求&#xff0c;自动填充“我出生于y年&#xff0c;直到x岁才遇到n个…

ElementUI之动态树+数据表格+分页

目录 一.ElementUI动态树 编写left.vue界面 配置url ​编辑 建立书籍的页面 编写路由 显示子主键的类 测试结果 二.ElementUI数据表格分页 在booklist中编写数据表格和分页 编写url路径 测试结果 ​编辑 一.ElementUI动态树 编写left.vue界面 <template>…

OpenAI 更新 ChatGPT:支持图片和语音输入【附点评】

一、消息正文 9月25日消息,近日OpenAI宣布其对话AI系统ChatGPT进行升级,添加了语音输入和图像处理两个新功能。据OpenAI透露,这些新功能将在未来两周内面向ChatGPT Plus付费用户推出,免费用户也将很快可以使用这些新功能。这标志着ChatGPT继续朝着多模态交互的方向发展,为用户提…

3D 视觉市场空间广阔,3D 感知龙头全技术路线布局

3D 视觉市场尚处在发展早期,空间广阔 人类 70%以上信息通过眼睛获取,对于机器而言,视觉感知也是其“智能化”升级的重要基础。3D 成像让每一个像素除 x、y 轴数据外,还有 z 轴(深度/距离)数据。围绕着人体、物体、空间扫描一圈,就能得到点云图和精准的“1:1”还原的 3D …

abaqus命令行基础

1.abaqus命令行基础 使用 abaqus 时&#xff0c;如果需要进行参数分析&#xff0c;就需要生成大量的模型&#xff0c;这时一般会使用python文件定义函数进行批量生成并计算。 如果已经生成了计算文件(*.inp文件)&#xff0c;那么就可以直接在命令行进行求解&#xff0c;abaqu…