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

C语言--动态内存管理1

目录

  • 前言
  • 动态内存函数介绍
    • malloc
    • free
    • calloc
    • realloc
  • 常见的动态内存错误
    • 对NULL指针的解引用操作
    • 对动态开辟空间的越界访问
    • 对非动态开辟内存使用free释放
    • 使用free释放一块动态开辟内存的一部分
    • 对同一块动态内存多次释放
    • 动态开辟内存忘记释放(内存泄漏)
  • 对通讯录进行优化

前言

我们目前已知的内存开辟方式是要指定长度的,比如以下这两种方式

int val = 20;
char arr[10] = {0};

我们发现:

  1. 这些空间开辟的大小时固定的,无法被修改
  2. 如果是数组,在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配.

再比如我们的通讯录,如果我们一开始就确定了人员个数的最大值,那么一旦到达这个上限,就无法继续录入数据了,我们能不能实现一种能根据我们的需求灵活更改空间大小的方式呢?这时候我们就需要动态内存开辟空间了。

动态内存函数介绍

malloc

我们先来看书写的格式:

     void* malloc (size_t size);

malloc本质上是一个函数,它的作用是开辟内存块,它有参数,有返回值,我们现在来看看分别是什么:

1. 如果开辟空间成功,则返回一个指向开辟好空间的指针

2. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定
因为malloc这个函数的返回值是void类型的指针,所以我们在接收的时候,要强制力类型转换为我们想要的类型,并用相应的指针来接收。比如我们要申请5个整型*的内存空间,代码就应这样书写:

     int* p=(int)malloc(20);//注意这里的20是指20个字节,即5个整型大小

值得注意的是,此时我们申请的动态内存空间是在堆区的,这有别于我们之前在栈区开辟空间。

3. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
我们知道,对空指针解引用操作是很危险的,所以我们一定要在使用前检查一下,代码如下

//申请int* p = (int*)malloc(20);if (p == NULL){printf("%s\n", strerror(errno));return 1;}

4. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器

free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的.
书写格式如下:

void free (void* ptr);
  1. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  2. 如果参数 ptr 是NULL指针,则函数什么事都不做
    我们将 p free掉后看一下p的地址是否发生改变。
    图1
    我们发现,虽然空间被回收了,但是p内存放的地址并没有改变,这时候如果再对其解引用便是非法访问了,所以我们要手动将p的值赋为NULL,具体代码实现如下
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>int main()
{int* p = (int*)malloc(20);if (p == NULL){printf("%s\n", strerror(errno));return 1;}free(p);p = NULL;return 0;
}

备注:mallocfree都声明在 stdlib.h 头文件中。

calloc

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);
  1. 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
  2. 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的**每个字节初始化为全0

假设我们要申请十个int类型大小的空间,书写方式如下

int *p = (int*)calloc(10, sizeof(int));

realloc

我们看这个函数的前缀re,联系英语常识可以大概猜到这个函数的用途是重新,再次开辟一块空间。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整

书写格式如下

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

1. ptr要调整的内存地址
2. size调整之后新大小
3. 返回值调整之后的内存起始位置

要注意区分的是:realloc在调整内存空间的是存在两种不同情况,分别是原有空间之后有足够大的空间原有空间之后没有足够大的空间

情况1: 原有空间之后有足够大的空间
图2
此时,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2: 原有空间之后没有足够大的空间
图3
此时,realloc会找更大的空间(找不到就开辟失败了),将原来的数据拷贝到新的空间去,释放旧的空间,返回新空间的地址。

常见的动态内存错误

对NULL指针的解引用操作

我们来看下面这段代码

void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;
free(p);
}

我们在前面讲到过,如果malloc函数开辟空间失败,就会返回空指针,对空指针解引用很明显存在问题。

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

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;
}
free(p);
}

上面这段代码当i=10的时候,已经超出我们开辟的空间范围了,这时候再解引用就会越界访问

对非动态开辟内存使用free释放

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

我们前面说过: 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。所以这里我们对非动态开辟内存使用free释放肯定是行不通的

使用free释放一块动态开辟内存的一部分

void test()
{
int *p = (int *)malloc(100);
p++;
free(p);
}

看这段代码我们发现,p++使得p不再指向动态内存开辟的起始位置,这样写是有问题的。

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

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);
}

这样写是有问题的,但是如果我们释放完一次就将p赋值为空指针,这时候再对p free就不会有任何问题了。

void test()
{
int *p = (int *)malloc(100);
free(p);
p=NULL;
free(p);
}

我们在前面也讲过,如果参数 ptr 是NULL指针,则函数什么事都不做

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

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

我们首先要了解一点的是:以上的所有函数所申请的空间,如果不想使用,需要free释放,如果不想使用free释放,在程序结束之后,也会由操作系统回收
但是,如果不使用free释放,程序也不结束,就会造成内存泄露

这里尤其要注意的点是,如果动态内存函数开辟空间是在另一个函数内部进行的,出了这个函数,这块空间是没有像局部变量一样被回收的,只有free能将它回收!!!

对通讯录进行优化

在学习了动态内存函数后,我们可以将其灵活运用到通讯录中,实现可以对达到人数上限的通讯录进行扩容。
主要修改点在于
1,初始化
我们假定初始大小为3,每次扩容大小为2。这里我们可以用,方便修改。
同时,因为我们需要对当前容量与最大容量比较决定是否扩容,所以还需一个参数capacity来存放最大容量的的数值。

#define DEFAULT_SZ 3
#define INC_SZ 2void InitContact(Contact* pc)
{pc->sz = 0;pc->data = malloc(DEFAULT_SZ * sizeof(PeoInfo));if (pc->data == NULL){printf("通讯录初始化失败:%s", strerror(errno));return;}pc->sz = 0;pc->capacity = DEFAULT_SZ;
}

2,扩容

void CheckCapacity(Contact* pc)
{if (pc->sz == pc->capacity)//判断是否达到容量上限{PeoInfo* ptr = realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));if (ptr == NULL){printf("扩容失败:%s\n",strerror(errno));return;}else{pc->data = ptr;pc->capacity += INC_SZ;printf("扩容成功,当前容量:%d\n", pc->capacity);}}
}

3,销毁

void DestroyContact(Contact* pc)
{free(pc->data);pc->data = NULL;pc->capacity = 0;pc->sz = 0;
}

以上就是本章全部内容,如有出入,欢迎指正。

相关文章:

C语言--动态内存管理1

目录前言动态内存函数介绍mallocfreecallocrealloc常见的动态内存错误对NULL指针的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放一块动态开辟内存的一部分对同一块动态内存多次释放动态开辟内存忘记释放&#xff08;内存泄漏&#xff09;对通讯…...

HTTPS 的工作原理

1、客户端发起 HTTPS 请求 这个没什么好说的&#xff0c;就是用户在浏览器里输入一个 https 网址&#xff0c;然后连接到 server 的 443 端口。 2、服务端的配置 采用 HTTPS 协议的服务器必须要有一套数字证书&#xff0c;可以自己制作&#xff0c;也可以向组织申请&#xf…...

游戏开发中建议使用半兰伯特光照

游戏开发中建议使用半兰伯特光照模型 在基本光照模型中求出漫反射部分的计算公式: 漫反射 = 入射光线的颜色和强度(c light) * 材质漫反射系数 (m diffuse)* 表面法线(n) * 其光源防线 (I) 在shader中为了不让 n和i的点乘结果为负数,即使用了saturate函数让值截取在[0,1]区…...

JavaScript到底如何存储数据?

1.var的迷幻操作 普遍的观点&#xff1a;JavaScript中的基本数据类型是保存在栈空间&#xff0c;而引用数据类型则是保存在堆空间里, 是否正确&#xff1f; 浏览器环境下JavaScript变量类型的运行实践结果: var a 10;console.log(a);console.log(window.a); console.log(wind…...

python实战应用讲解-【numpy专题篇】numpy应用案例(一)(附python示例代码)

目录 用Python分析二手车的销售价格 用Python构建GUI应用的铅笔草图 需要的包 实现步骤 完整代码 用Python分析二手车的销售价格 如今&#xff0c;随着技术的进步&#xff0c;像机器学习等技术正在许多组织中得到大规模的应用。这些模型通常与一组预定义的数据点一起工作…...

网络割接项目

某企业准备采购2台华为设备取代思科旧款设备,针对下列问题作出解答。 (1)做设备替换的时候,如何尽可能保证业务稳定性,请给出解决方案。 a)对现网拓扑进行分析,分析现网拓扑的规划(链路类型、cost、互联IP、互联接口等信息)、分析现网流量模型(路由协议、数据流向特…...

SpringBoot整合数据可视化大屏使用

1 前言 DataV数据可视化是使用可视化应用的方式来分析并展示庞杂数据的产品。DataV旨让更多的人看到数据可视化的魅力,帮助非专业的工程师通过图形化的界面轻松搭建专业水准的可视化应用,满足您会议展览、业务监控、风险预警、地理信息分析等多种业务的展示需求, 访问地址:h…...

蓝桥杯Web前端练习题-----水果拼盘

一、水果拼盘 介绍 目前 CSS3 中新增的 Flex 弹性布局已经成为前端页面布局的首选方案&#xff0c;本题可以使用 Flex 属性快速完成布局。 准备 开始答题前&#xff0c;需要先打开本题的项目代码文件夹&#xff0c;目录结构如下&#xff1a; ├── css │ └── style.…...

[攻城狮计划]如何优雅的在RA2E1上运行RT_Thread

文章目录[攻城狮计划]|如何优雅的在RA2E1上运行RT_Thread准备阶段&#x1f697;开发板&#x1f697;开发环境&#x1f697;下载BSP&#x1f697;编译烧录连接串口总结[攻城狮计划]|如何优雅的在RA2E1上运行RT_Thread &#x1f680;&#x1f680;开启攻城狮的成长之旅&#xff0…...

1.linux操作命令

1. pwd -> 打印当前绝对工作路径。 2. ls -> 查看目录的文件名 ls -> 默认列出当前目录的全部文件名 ls . -> 列出当前目录的全部文件名(.代表当前目录) ls / -> 列出根目录下的全部文件命名 ls -a -> 列出当前目录下全部文件名(包括隐藏…...

STL--vector

vector 头文件 #include<vector>向量的定义&#xff1a; vector<int> vec&#xff1b;//定义一个vec型的向量a vector<int> vec(5); //定义一个初始大小为5的向量 vector<int> vec(5,1); //初始大小为5&#xff0c;值都为1的向量二维数组&#xff1…...

Java每日一练(20230324)

目录 1. 链表插入排序 &#x1f31f;&#x1f31f; 2. 最接近的三数之和 &#x1f31f;&#x1f31f; 3. 寻找旋转排序数组中的最小值 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一…...

你掌握了吗?在PCB设计中,又快又准地放置元件

在印刷电路板设计中&#xff0c;设置电路板轮廓后&#xff0c;将零件(占地面积)调用到工作区。然后将零件重新放置到正确的位置&#xff0c;并在完成后进行接线。 组件放置是这项工作的第一步&#xff0c;对于之后的平滑布线工作是非常重要的工作。如果在接线工作期间模块不足…...

springboot学生综合测评系统

031-springboot学生综合测评系统演示录像2022开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&…...

【Unity3D】法线贴图和凹凸映射

1 法线贴图原理 表面着色器中介绍了使用表面着色器进行法线贴图&#xff0c;实现简单快捷。本文将介绍使用顶点和片元着色器实现法线贴图和凹凸映射&#xff0c;实现更灵活。 本文完整代码资源见→法线贴图和凹凸映射。 1&#xff09;光照原理 Phong 光照模型和 Blinn Phong 光…...

代码误写到master分支(或其他分支),此时代码还未提交,如何转移到新建分支?

问题背景 有时候&#xff0c;我们拿到需求&#xff0c;没仔细看当前分支是什么&#xff0c;就开始撸代码了。完成了需求或者写到一半发现开发错分支了。 比如此时新需求代码都在master分支上&#xff0c;提交必然是不可能的&#xff0c;所有修改还是要在新建分支上进行&#x…...

java多线程之线程安全(重点,难点)

线程安全1. 线程不安全的原因:1.1 抢占式执行1.2 多个线程修改同一个变量1.3 修改操作不是原子的锁(synchronized)1.一个锁对应一个锁对象.2.多个锁对应一个锁对象.2.多个锁对应多个锁对象.4. 找出代码错误5. 锁的另一种用法1.4 内存可见性解决内存可见性引发的线程安全问题(vo…...

如何免费使用chatGPT4?无需注册!

Poe体验真滴爽首先提大家问一个大家最关心的问题如何在一年内赚到一百万&#xff1f;用个插件给他翻译一下体验地址效果是非常炸裂的&#xff0c;那么我就将网址分分享给大家https://poe.com/前提&#xff1a;要有魔法&#xff0c;能够科学shangwangChatGPT-3 随便问GPT-4 模型…...

Android Flutter在点击事件上添加动画效果

在Android App的开发项目中&#xff0c;我们需要在点击事件上实现一个动画效果来提高用户的体验度。比如闲鱼底部中间按钮的那种。该怎么实现呢&#xff1f; 一起来看看吧 实现效果如图&#xff1a; ​实现思路 根据UI的设计图&#xff0c;对每个模块设计好动画效果&#xff0…...

VSCode嵌入式开发环境搭建

Vscode开发环境搭建 看这个链接就可以了&#xff0c;后面下载调试有点问题看下3.3。 在VSCode上部署STM32F1的开发环境 1. MXCube配置工程生成Makefile文件 借助正确的编译工具链进行编译&#xff0c; 2. 编译工具链搭建 编译工具链使用GCC的ARM版本 arm-none-eabi-gcc &am…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

Linux系统部署KES

1、安装准备 1.版本说明V008R006C009B0014 V008&#xff1a;是version产品的大版本。 R006&#xff1a;是release产品特性版本。 C009&#xff1a;是通用版 B0014&#xff1a;是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存&#xff1a;1GB 以上 硬盘&#xf…...