动态内存管理篇

为什么要动态内存分配?

之前,我们向内存申请空间,有两种方式,一种是定义变量,一种是建立数组;但是,这两种方式都有缺陷,创建的空间大小是固定的,在程序的运行过程中,不能随着我们的需要改变而改变,这就需要我们申请动态内存了

1. 动态内存函数


1.1 malloc和free

void* malloc (size_t size);

函数功能:

  • 开辟一块size字节大小的空间
  • 如果开辟成功,返回开辟空间首地址
  • 如果开辟失败,返回NULL
  • 如果size是0,标准未定义,取决于编译器
  • 由于返回值是void*类型,因此返回的地址需要我们另做处理
void free (void* ptr);

函数功能:

  • 释放ptr指向的空间,前提是ptr指向的空间是动态开辟的;如果ptr指向的空间不是动态开辟的,编译器会报错
  • 如果ptr为NULL,则什么都不做
  • 另外,释放完空间,ptr此时是一个野指针,需要置空

动态内存函数要和free一同使用,动态开辟的空间有两种方式释放:

  1. free主动释放
  2. 程序结束,操作系统会帮我们回收

虽然程序结束,申请的动态空间也会被回收,但如果程序在退出之前,开辟了多处动态内存而没有释放,又去开辟动态内存,很可能会导致内存泄漏,因此,每次申请的动态内存都要记得free释放

int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}for (int i = 0; i < 10; i++){p[i] = i;}for (int i = 0; i < 10; i++){printf("%d ", p[i]);}free(p);p = NULL;return 0;
}
//输出:0 1 2 3 4 5 6 7 8 9

内存非为三大部分,动态内存函数申请的空间是在堆上开辟的在这里插入图片描述

1.2 calloc函数

void* calloc (size_t num, size_t size);

函数功能:

  • num是开辟元素的个数,size是每个元素的大小,开辟num*size字节的空间
  • calloc开辟空间后,会将空间自动初始化为0

在这里插入图片描述

1.3 realloc函数

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

动态开辟的内存用完了,想进行增容,这时就可以考虑使用realloc

函数功能:

  • ptr是要进行扩容的地址,size为新的空间大小
  • 返回新空间的地址
  • 如果ptr为空,此时就相当于malloc函数

开辟空间有两种情况:

  1. ptr后面的空间够存放新空间的大小:此时直接在ptr后面的空间扩容
  2. ptr后面的空间不够存放新空间的大小:此时会另开辟一块size大小的空间,并把原数据拷贝到新空间,释放掉旧空间,返回新空间的地址
int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//...//想扩容到100个int的大小//写法1p = realloc(p, 100 * sizeof(int));//写法2int* ptr = (int*)realloc(p, 100 * sizeof(int));if (ptr == NULL){perror("realloc");return 1;}p = ptr;//...free(p);p = NULL;return 0;
}

用realloc开辟完空间,更推荐使用写法2,因为realloc开辟空间可能会失败,此时返回NULL,不仅没有扩容成功,还把原来的空间给弄没了

2.动态内存常见的错误


2.1对空指针进行解引用

这种情况通常是因为使用完动态内存函数没有对返回值进行检查

在这里插入图片描述

2.2对动态内存的越界访问

int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}for (int i = 0; i <= 10; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}

i=10的内存已经不属于我们的了;这种情况编译器是不会报错的,需要我们自己擦亮眼睛

2.3对非动态内存使用free

int main()
{int p[10] = { 0 };for (int i = 0; i < 10; i++){p[i] = i;}free(p);return 0;
}

p指向的空间不是动态开辟的,不能进行free释放

2.4使用free释放一部分动态内存

int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}for (int i = 0; i < 5; i++){*p = i;p++;}free(p);p = NULL;return 0;
}

p最终指向动态内存的一部分,free§只释放了一部分,最终仍有可能造成内存泄漏

2.5对一块动态内存多次释放

int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//...free(p);//...free(p);p = NULL;return 0;
}

前面说过,free指向的空间必须是动态内存,第二次free时,p指向的空间已经不是动态的了

2.6忘记释放动态内存

void Print(int n)
{int* p = (int*)malloc(n * sizeof(int));if (p == NULL){perror("malloc");return;}if (n == 3)return;free(p);p = NULL;
}int main()
{Print(3);return 0;
}

上面的代码写了free且置空,看似没有问题,但在执行free之前,函数已经返回,不会执行free,因此没有释放成功

内存泄漏是非常严重的问题,在日常写代码的过程中,一定要注意,动态开辟的内存要记得释放

3.笔试题讲解


题目1:

void GetMemory(char* p)
{p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}int main()
{Test();return 0;
}

上述代码的运行结果是什么?

  • 由于p是str的一份临时拷贝,出了GetMemory函数就销毁了,malloc开辟出来的空间就找不到了,导致内存泄漏
  • 执行完GetMemory函数,str仍然是NULL,在strcpy函数中,会对str解引用,对空指针进行解引用,最终导致程序崩溃

怎么修改上述代码,让它达到我们想要的功能?

char* GetMemory()
{char* p = (char*)malloc(100);return p;
}void Test(void)
{char* str = NULL;str = GetMemory();strcpy(str, "hello world");printf(str);free(str);str = NULL;
}int main()
{Test();return 0;
}

题目2:

char* GetMemory(void)
{char p[] = "hello world";return p;
}void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}int main()
{Test();return 0;
}

p是GetMemory函数的局部变量,出了函数就销毁了,此时返回的p被外面的str接受,str就变成了野指针,打印的是一堆乱码,这是一种返回栈空间地址的问题

上面的代码也可以简化为:

int* Test()
{int a = 10;return &a;
}int main()
{int* p = Test();printf("%d\n", *p);//10return 0;
}

同样是犯了返回栈空间地址的错误,我们发现该代码输出是正常的,这是为什么?

虽然结果正确,但这并不代表代码没有问题,结果正确的原因是Test函数即使销毁了,p位置处的值仍没有被修改,因此误打误撞,结果是对的在这里插入图片描述

题目3:

void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}int main()
{Test();return 0;
}

该代码正常打印,唯一的缺点就是少了释放内存,存在内存泄漏的问题

题目4:

void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}

str指向的空间释放后,没有置空,str此时是野指针,对野指针进行了访问,非法访问内存空间

题目5:

int* Test()
{int* p;*p = 10;return p;
}

创建p时没有对其初始化,p为随机值,随机指向一块空间,是野指针,对野指针进行了操作

4.C/C++的内存区域


在这里插入图片描述

  • 栈区:执行函数时,函数内的局部变量都是在该区域创建的,当函数结束时,自动销毁创建的区域
  • 堆区:动态开辟的空间在该区域创建,通常由程序员自己释放,当程序结束时,也会由操作系统自动回收
  • 数据段:存放全局变量,静态数据,程序结束由系统释放
  • 代码段:存放函数体的二进制代码

6.柔性数组

定义:在结构体中,最后一名成员是数据,且数组的大小未知,我们把该数组叫做柔性数组

struct S
{char c;int i;int arr[];
};

6.1柔性数组的特点

  1. 柔性数组必须在结构体当中
  2. 必须是最后一名成员
  3. 柔性数组前面至少有一名成员
  4. 该结构体的大小不包括数组的大小
  5. 必须用动态内存函数对结构体开辟空间,且开辟空间的大小要大于结构体的大小,确保柔性数组有有一定的空间

6.2柔性数组的使用

//代码1
struct S
{char c;int i;int arr[];
};int main()
{struct S* p;p = (struct S*)malloc(sizeof(struct S) + 20);if (p == NULL){perror("malloc");return 1;}p->c = 'a';p->i = 5;for (int i = 0; i < 5; i++){p->arr[i] = i;}//空间不够了,进行增容struct S* ptr = (struct S*)realloc(p, sizeof(struct S) + 40);if (ptr == NULL){perror("realloc");return 1;}p = ptr;ptr = NULL;for (int i = 0; i < 10; i++){p->arr[i] = i;}free(p);p = NULL;return 0;
}

实际上,不用柔性数组也能完成上面的操作

//代码2  
struct S  
{char c;  int i;  int* arr;  
};int main()  
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL) {perror("malloc");return 1;}int* ptr = (int*)malloc(sizeof(int) * 5);if(ptr == NULL){perror("malloc");return 1;}ps->arr = ptr;ptr = NULL;for (int i = 0; i < 5; i++){ps->arr[i] = i;}//增容int* p = (int*)realloc(ps->arr, sizeof(int) * 10);if (p == NULL){perror("malloc");return 1;}ps->arr = p;p = NULL;for (int i = 0; i < 10; i++){ps->arr[i] = i;}free(ps->arr);ps->arr = NULL;free(ps);ps = NULL;
}

那么,使用柔性数组的代码1相较于代码2,有什么优势呢?

  1. 方便内存的释放,使用柔性数组只需要释放一次内存空间;而代码2你必须先将结构体成员开辟的空间释放后,才能释放结构体,多释放意味着风险越多

动态内存的内容就到这!

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

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

相关文章

买对好车省钱又防坑,高性价比的买车攻略

一、教程描述 正所谓隔行如隔山&#xff0c;买车这件事情并不简单&#xff0c;买车的内幕还是有不少的&#xff0c;本套教程讲述买车攻略&#xff0c;非常适合准备买车的朋友&#xff0c;可以帮助大家买车少入坑&#xff0c;高性价比买到自己心仪的车。本套买车教程&#xff0…

2023-12-23 LeetCode每日一题(移除石子使总数最小)

2023-12-23每日一题 一、题目编号 1962. 移除石子使总数最小二、题目链接 点击跳转到题目位置 三、题目描述 给你一个整数数组 piles &#xff0c;数组 下标从 0 开始 &#xff0c;其中 piles[i] 表示第 i 堆石子中的石子数量。另给你一个整数 k &#xff0c;请你执行下述…

【随口一说】最近的CSDN

这段时间随便发的一篇博文很快就有“点赞”、“收藏”、“关注”的信息&#xff0c; 而且简单看了一眼用户&#xff0c;很多都是空的或者一堆转载&#xff0c; 机器人也太明显了点&#xff0c;很让人不舒服&#xff0c; 不花点心思设计文章评优推送算法反倒用机器人刷热门&…

电机(一):直流有刷电机和舵机

声明&#xff1a;以下图片来自于正点原子&#xff0c;仅做学习笔记使用 电机专题&#xff1a; 直流电机&#xff1a;直流有刷BDC&#xff08;内含电刷&#xff09;&#xff0c;直流无刷BLDC&#xff08;大疆的M3508和M2006&#xff09;,无刷电机有以下三种形式&#xff1a;&a…

C语言之分支与循环【附6个练习】

文章目录 前言一、什么是语句&#xff1f;1.1 表达式语句1.2 函数调用语句1.3 控制语句1.4 复合语句1.5 空语句 二、分支语句&#xff08;选择结构&#xff09;2.1 if语句2.1.1 悬空else2.1.2 练习&#xff08;1. 判断一个数是否为奇数 2. 输出1-100之间的奇数&#xff09; 2.2…

deepfacelive实时换脸教程(2024最新版)

deepfacelive其实操作用法很简单&#xff0c;难的是模型的制作。本帖主要讲deepfacelive&#xff08;下文简称dflive&#xff09;软件本身的操作&#xff0c;以及模型怎么从dfl转格式过来&#xff0c;至于模型如何训练才能效果好&#xff0c;请移步教程区&#xff0c;看deepfac…

51单片机中TCON, IE, PCON等寄存器的剖析

在单片机中&#xff0c;如何快速通过名字记忆IQ寄存器中每一个控制位的作用呢&#xff1f; IE&#xff08;interrupt enable&#xff09;寄存器中&#xff0c;都是中断的使能位置。 其中的EA&#xff08;enable all&#xff09;是总使能位&#xff0c;ES(enable serial)是串口…

构建安全的SSH服务体系

某公司的电子商务站点由专门的网站管理员进行配置和维护&#xff0c;并需要随时从Internet进行远程管理&#xff0c;考虑到易用性和灵活性&#xff0c;在Web服务器上启用OpenSSH服务&#xff0c;同时基于安全性考虑&#xff0c;需要对 SSH登录进行严格的控制&#xff0c;如图10…

WorkQueue模型

WorkQueues&#xff0c;也被称为任务队列模型。当消息处理比较耗时的时候&#xff0c;可能生产消息的速度会远远大于消息的消费速度。长此以往&#xff0c;消息就会堆积越来越多&#xff0c;无法及时的处理。此时就可以使用work模型&#xff1a;让多个消费者绑定到一个队列&…

gem5学习(8):创建一个简单的缓存对象--Creating a simple cache object

目录 一、SimpleCache SimObject 二、Implementing the SimpleCache 1、getSlavePort() 2、handleRequest() 3、AccessEvent() 4、accessTiming() &#xff08;1&#xff09;缓存命中&#xff1a;sendResponse() &#xff08;2&#xff09;缓存未命中&#xff1a; 三、…

matlab概率论例子

高斯概率模型&#xff1a; [f,xi] ksdensity(x): returns a probability density estimate, f, for the sample in the vector x. The estimate is based on a normal kernel function, and is evaluated at 100 equally spaced points, xi, that cover the range of the da…

Mybatis行为配置之Ⅰ—缓存

专栏精选 引入Mybatis Mybatis的快速入门 Mybatis的增删改查扩展功能说明 mapper映射的参数和结果 Mybatis复杂类型的结果映射 Mybatis基于注解的结果映射 Mybatis枚举类型处理和类型处理器 再谈动态SQL Mybatis配置入门 Mybatis行为配置之Ⅰ—缓存 Mybatis行为配置…

读书笔记1-C++ Primer Plus

C是在C语言基础上开发的一种集面向对象编程&#xff08;OOP&#xff09;、通用编程和传统的过程化编程于一体的编程语言。本书是根据2003年的ISO/ANSI C标准编写的&#xff0c;通过大量短小精悍的程序详细而全面地阐述了C的基本概念和技术。 全书分17章和10个附录&#xff0c;分…

使用WAZUH检测LD_PRELAOD劫持、SQL注入、主动响应防御

目录 1、检查后门 使用工具检测后门 1.chkrootkit 2.rkhunter 手动检查文件 检查ld.so.preload文件 2、检测LD_PRELOAD ubuntu配置 wazuh配置 3、检测SQL注入 ubuntu配置 攻击模拟 4、主动响应 wauzh的安装以及设置代理可以参考本篇&#xff1a;WAZUH的安装、设置…

Apache Flink连载(二十三):Flink HA - Flink基于Yarn HA

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. Yarn HA配置 ​​​​…

Cache替换算法

由于Cache很小&#xff0c;主存很大&#xff0c;Cache很容易装满&#xff0c;Cache满了怎么办&#xff1f; ——采用替换算法。 全相联映射&#xff1a;Cache完全满了才需要替换&#xff0c;需要在全局中选择替换哪一块。直接映射&#xff1a;如果对应位置非空&#xff0c;则…

linux线程与进程

简要 在Linux系统中&#xff0c;进程&#xff08;Process&#xff09;和线程&#xff08;Thread&#xff09;是操作系统中两个重要的概念&#xff0c;它们都是用于执行程序的执行单元&#xff0c;但有一些关键的区别。 在Linux系统中&#xff0c;可以使用fork系统调用创建新…

Vue3-30-路由-嵌套路由的基本使用

什么是嵌套路由 嵌套路由 &#xff1a;就是一个组件内部还希望展示其他的组件&#xff0c;使用嵌套的方式实现页面组件的渲染。 就像 根组件 通过路由渲染 普通组件一样&#xff0c;嵌套路由也是一样的道理。 嵌套路由的相关关键配置 1、<router-view> 标签 声明 被嵌套组…

在 Spring 中操作 Redis

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对博主首页也很感兴趣o (ˉ▽ˉ&#xff1b;) &#x1f4dc;redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿 目录 1、引入依赖 2、对 Redis 的配置文件进行书写 3、S…

kivy PageLayout 的说明及例子

PageLayout 是 Kivy GUI 框架中的一个布局管理器&#xff0c;它允许开发者在同一个窗口中放置多个页面&#xff0c;用户可以通过滑动来浏览这些页面。PageLayout 的工作方式类似于一个可以滑动的标签页&#xff08;TabbedPanel&#xff09;&#xff0c;但其页面可以自由调整大小…