动态内存管理篇

为什么要动态内存分配?

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

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;请你执行下述…

chromium通信系统-ipcz系统(七)-ipcz系统代码实现-跨Node通信-NonBroker和NonBroker通信

在chromium通信系统-ipcz系统(六)-ipcz系统代码实现-跨Node通信-基础通信 一文中我们分析了broker 和 nonbroker 通信的过程。本文我们来分析NonBroker 和NonBroker的通信过程&#xff0c;同样以单元测试为例子分析。 mojo/core/invitation_unittest.cc 951 DEFINE_TEST_CLIEN…

面试算法:快速排序

题目 快速排序是一种非常高效的算法&#xff0c;从其名字可以看出这种排序算法最大的特点就是快。当表现良好时&#xff0c;快速排序的速度比其他主要对手&#xff08;如归并排序&#xff09;快2&#xff5e;3倍。 分析 快速排序的基本思想是分治法&#xff0c;排序过程如下…

Python期末复习资料

一、基础知识点 1. Python基础语法 变量与数据类型 定义变量&#xff0c;理解变量的命名规则基本数据类型&#xff1a;整数、浮点数、字符串列表、元组、字典、集合等复合数据类型 1. 1 变量 1.1.1 变量的定义 在Python中&#xff0c;变量是用来存储数据值的标识符。你可…

.Net8 新特性之键控服务-依赖注入一对多模式

在.NET8 中引入了KeyedService支持&#xff0c;这使得可以支持一对多的依赖注入了。在官方&#xff0c;这个功能叫键化DI服务。 什么是键化DI服务&#xff1f;官方解释是这样的&#xff0c;键化依赖项注入&#xff08;DI&#xff09;服务提供了一种适用键来注册和检索DI服务的…

【随口一说】最近的CSDN

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

问题描述:智能驾驶中的FSD是什么意思。

问题描述&#xff1a;智能驾驶中的FSD是什么意思。 问题描述&#xff1a; FSD 在智能驾驶领域通常指的是 "Full Self-Driving"&#xff0c;即全自动驾驶。这表示汽车具备了足够的智能和技术&#xff0c;能够在不需要人类干预的情况下完成所有驾驶任务。全自动驾驶系…

硬件知识之ESD保护器件

ESD保护器件&#xff0c;全称是Electrostatic Discharge Protection Device&#xff0c;是一种专门用与防止电子设备遭受外部静电放电而损坏的元器件。静电放电会在接口或器件表面积累成千上万伏特的能量&#xff0c;这些能量可能会引起设备故障或者持久性损伤&#xff0c;甚至…

185.【2023年华为OD机试真题(C卷)】报文重排序(顺序索引实现JavaPythonC++JS)

请到本专栏顶置查阅最新的华为OD机试宝典 点击跳转到本专栏-算法之翼:华为OD机试 🚀你的旅程将在这里启航!本专栏所有题目均包含优质解题思路,高质量解题代码,详细代码讲解,助你深入学习,深度掌握! 文章目录 【2023年华为OD机试真题(C卷)】报文重排序(顺序索引…

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

声明&#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;让多个消费者绑定到一个队列&…

问题描述:与编码器对应的解码器是什么,说明一下解码器的名字由来,结构,原理,特点,用处。

问题描述&#xff1a;与编码器对应的解码器是什么&#xff0c;说明一下解码器的名字由来&#xff0c;结构&#xff0c;原理&#xff0c;特点&#xff0c;用处。 问题解答&#xff1a; 定义&#xff1a;解码器是一种电子设备或程序&#xff0c;用于将经过编码的数据转换回原始…

概率论基础

1.概率论 1.1 随机事件与概率 1.1.1 基本概念 ​ 样本点(sample point)&#xff1a; 称为试验 S S S的可能结果为样本点&#xff0c;用 ω \omega ω表示。 ​ 样本空间(sample space)&#xff1a;称试验 S S S的样本点构成的集合为样本空间&#xff0c;用 Ω \Omega Ω表示…

R语言【CoordinateCleaner】——cc_gbif(): 根据通过 method 参数定义的方法,删除或标记地理空间中异常值的记录。

cc_gbif()是R语言包coordinatecleaner中的一个函数&#xff0c;用于清理GBIF&#xff08;全球生物多样性信息设施&#xff09;数据集的地理坐标。该函数可以识别潜在的坐标错误&#xff0c;并对其进行修正或删除。 以下是cc_gbifl()函数的一般用法和主要参数&#xff1a; cc_…

把form表单数据转为json,并传给父页面

阻止form表单提交&#xff0c;表单数据转为json字符串&#xff0c;并传给父页面 // 获取表单元素var form document.getElementById(myForm);// 监听表单提交事件form.addEventListener(submit, function(event) {// 在这里处理表单提交的逻辑var rental_id $("#c-id&q…