C语言【动态内存】

1.为什么要有动态内存

我们现在掌握的内存开辟方法有:

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

但是上述的方式有两个点要注意:

1.空间开辟的大小是固定的
2.数组在申明的时候,一定要指定数组的长度(就算是变长数组也是先给变量赋值,再根据变量的大小,来开辟空间),数组空间一旦确定了大小是不能调整的

但是我们在实际上对于内存的需求方式,绝不仅仅是上述的情况。有时候我们需要的空间大小是在程序运行的时候才能知道,数组是在编译时开辟空间的,这是就不能满足需求了。
这时C语言就引入了动态内存开辟,让程序员可以自己根据需求来申请和释放空间这样就比较灵活。

2.动态内存函数

要想学会动态开辟内存就必须掌握这四个函数malloc free calloc realloc

它们的头文件都为<stdlib.h>
他们开辟空间的大小单位都为字节。

2.1 malloc

函数原型:void* malloc(size_t size);
该函数向内存申请一块连续何用的空间,并返回指向这块空间的指针。

  1. 如果开辟成功,返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则会返回一个NULL指针,因此malloc的返回值一定要做检查。

因为返回类型是void*,所以malloc函数并不知道开辟空间时是什么类型,在实际使用时类型是有使用者决定的。

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

代码如下:

#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");exit(1);}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}return 0;
}

在这里插入图片描述

注意:malloc开辟的空间并未进行初始化,里面是随机值

#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");exit(1);}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}return 0;
}
#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");}for (int i = 0; i < 10; i++){//未进行初始化//打印printf("%d ", *parr + i);}return 0;
}

结果如下:
在这里插入图片描述

这些动态开辟的空间是存放在哪里呢?

如图:
在这里插入图片描述

2.2 free

申请了空间在使用完的时候肯定是要还回去的嘛,那怎么还呢?

C语言提供了另外一个函数free,这是专门用来做动态内存的释放和回收的,函数原型如下:
void free(void* ptr)

  • 如果参数ptr指向的空间不是动态内存的时候,free函数的行为是未被定义的
  • 如果参数ptrNULL指针,那么free函数什么都不会做

注意:传递给free函数的参数是要被释放空间的起始地址
代码如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}//只用完动态申请的空间要进行释放free(parr);parr = NULL;return 0;
}

​free仅仅是将空间的使用权限还给了操作系统;
​但parr还指向原来的地址,这就成为野指针;
为了避免成为野指针,及时将parr置为NULL。

如果一直不用函数free来释放申请的空间,这样很可能会造成内存泄漏!!!

2.3 calloc

C语言还提供了一个函数叫calloccalloc函数也用来动态内存分配。原型如下:
void * calloc(size_t num, size_t size);

  • 函数的功能是将num个大小为size的元素开辟一块空间,并且会将开辟空间的每个字节初始化为0。
  • 函数calloc和函数malloc的区别就是calloc会在返回地址之前,把申请空间的每个字节初始化为0。

由于函数malloc比函数calloc少一步(将内存初始化),所以malloccalloc更快,如果更追求效率的话可以使用malloc,如果不想自己初始化的话可以使用calloc

代码如下:

int main()
{int* pca = (int*)calloc(10,sizeof(int));if (pca == NULL)//判断是否开辟失败{perror("calloc");exit(1);//直接退出程序}printf("calloc: ");for (int i = 0; i < 10; i++){printf("%d ", *pca + i);}printf("\n\n");int* pma = (int*)malloc(10 * sizeof(int));if (pma == NULL){perror("malloc");exit(1);}printf("malloc: ");for (int i = 0; i < 10; i++){printf("%d ", *pma + i);}return 0;
}

结果如图:
在这里插入图片描述

2.4 realloc

realloc函数能让动态内存管理更加灵活。

有些时候我们会发现我们申请的空间太小了,有时候又感觉申请的空间太大了,那么为了合理的使用空间,我们就会对申请的空间大小进行灵活的调整,那么realloc函数就能做到对动态空间大小的调整。

函数原型如下:
void* realloc(void* ptr, size_t size)

  • ptr是要被调整的内存地址
  • size是调整之后该内存的大小
  • 返回值为调整之后的内存空间的起始位置

2.4.1 realloc在调整内存空间有三种情况

  1. 原空间后面有足够的空间用来调整。
  2. 原空间后面没有足够空间用来调整,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
  3. 空间调整失败,返回NULL(这是最坏的情况)
    如图所示
    在这里插入图片描述
    代码如下:
#include<stdio.h>
#include<stdlib.h>int main()
{//开辟int* parr = (int*)malloc(5 * sizeof(int));if (parr == NULL){perror("malloc");}//使用for (int i = 0; i < 5; i++){*(parr + i) = i + 1;}//假设要多插入5个int类型的数据//这时空间就不够了,需要调整int* parr = (int*)realloc(parr, 10 * sizeof(int));//上述代码合适吗//不合适,因为万一调整失败,会返回NULL//这时我原来开辟的空间就找不到了//所以正确的方法应该如下//先申请调整空间int* ptr = (int*)realloc(parr, 10 * sizeof(int));//调整成功if (ptr != NULL){//将申请的空间地址赋给parr(如果是情况二,parr的原空间已经被释放了)parr = ptr;}else{perror("realloc");}//处理要求……free(parr);return 0;
}

上述代码int* parr = (int*)realloc(parr, 10 * sizeof(int));代码合适吗?
完全不合适!!! 因为万一调整失败,会返回NULL;
这时我原来开辟的空间就找不到了。
所以正确的realloc使用方式是先创建一个指针变量来接收开辟的空间,如果判断ptr是否开辟成功,如果开辟成功。将ptr赋给parr

如果开辟失败parr指向的原空间也能继续使用。

补充:realloc也可以完成malloc的功能;
如果传的是NULL指针,realloc就会开辟一块新空间。

3.常见的动态内存的错误

3.1对NULL指针的解引用

具体如下:

#include<stdio.h>
#include<stdlib.h>int main()
{int* ptr = (int*)malloc(INT_MAX);*ptr = 20;//如果ptr是NULL,就会有问题return 0;
}

在这里插入图片描述
解决方法:开辟空间后,要第一时间判断是否空

3.2对动态开辟空间进行越界访问

具体如下:

#include<stdio.h>
#include<stdlib.h>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);
}int main()
{test();return 0;
}

解决方法:控制好访问的范围

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

具体如下:

#include<stdio.h>
#include<stdlib.h>int main()
{int a = 0;int* p = &a;//处理……free(p);//这样行吗,肯定不行!!!//a是局部变量,他开辟的空间是放在栈区的,而不是堆区的//free释放的是realloc、malloc、calloc开辟的空间(动态内存空间是在堆区的)return 0;
}
//这样行吗,肯定不行!!!
//a是局部变量,他开辟的空间是放在栈区的,而不是堆区的
//free释放的是realloc、malloc、calloc开辟的空间(动态内存空间是在堆区的)

在这里插入图片描述
编译都无法编译过去

解决方法:不要对非动态开辟空间进行释放!!!

3.4使用free释放的并不是动态开辟空间的起始位置

具体如下:

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置return 0;
}

在这里插入图片描述
同样也无法编译过去
解决方法:传给free的时候确保是空间的起始地址

3.5对同一块内存进行对此释放

具体如下:

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);free(p);free(p);return 0;
}

在这里插入图片描述
同样还是无法编译过去

解决方法:在第一次释放后及时的给p赋NULL

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);free(p);//解决方法p = NULL;free(p);return 0;
}

前面也说过了,如果传给free函数的是NULLfree不会进行如何的操作。

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

具体如下:

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();//出来后test就被销毁了,就无法找到p所指向的空间了while (1);
}

忘记释放不再使用的空间会造成内存泄漏
释放动态内存有两个方法:

  1. 在不用的时候使用free函数进行释放

  2. 如果一直没有被free释放,当程序运行结束后,会由操作系统来回收
    解决方法:
    1.谁申请的空间谁释放,如果在函数里,那么在出函数前记得释放malloc/calloc/realloc要和free成对出现。

    2.如果不能释放(后续会用到),要告诉下一个使用者,记得释放动态开辟空间。

切记:动态开辟的空间⼀定要释放,并且正确释放。

结语

最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言。如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!!!

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

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

相关文章

shell脚本,删除30天以前的日志,并将日志推送到nas,但运行出现/bin/bash^M。

删除30天以前的日志 将日志推送到nas中&#xff0c;然后删除pod中的日志 pod挂载到本地 运行出现/bin/bash^M 1、删除30天以前的日志&#xff1a; #! /bin/bash# 定义源日志目录 LOG_DIR/home/log/ # 删除日志 find $LOG_DIR -type f -name "*.log" -mtime 30 -exec…

线上线下收银一体化,新零售POS系统引领连锁门店数字化转型-亿发

在市场竞争日益激烈的背景下&#xff0c;没有哪个商家能够永远屹立不倒。随着互联网技术的快速发展&#xff0c;传统的线下门店面临着来自电商和新零售的新型挑战。实体零售和传统电商都需要进行变革&#xff0c;都需要实现线上线下的融合。 传统零售在客户消费之后就与商家失…

从MySQL+MyCAT架构升级为分布式数据库,百丽应用OceanBase 4.2的感受分享

本文来自OceanBase的客户&#xff0c;百丽时尚的使用和测试分享 业务背景 百丽时尚集团&#xff0c;作为国内大型时尚鞋服集团&#xff0c;在中国超过300个城市设有直营门店&#xff0c;数量超过9,000家。集团构建了以消费者需求为核心的垂直一体化业务模式&#xff0c;涵盖了…

FORM调用标准AP\AR\GL\FA界面

EBS FORM客户化界面有时候数据需要追溯打开AP\AR\GL\FA等界面&#xff1a; 一种打开日记账的方式&#xff1a; PROCEDURE SHOW_JOURNAL ISparent_form_id FormModule;child_form_id FormModule; BEGINclose_jrn;parent_form_id : FIND_FORM(:SYSTEM.CURRENT_FORM);COPY(TO…

安卓数据库SQLite

目录 一、SQLite数据库二、SQLiteOpenHelper和SQLiteDatabase2.1 SQLiteOpenHelper2.2 SQLiteDatabase 三、常见数据库使用介绍3.1 创建数据库3.2 插入数据3.3 修改数据&#xff08;升级数据库&#xff09;3.4 删除数据3.5 查询数据3.6 关闭数据库3.7 删除数据库 一、SQLite数据…

Apache中如何配置 ws 接口

Apache中如何配置 wss 接口 在Apache中配置WebSockets的支持&#xff0c;你需要使用mod_proxy_wstunnel模块&#xff0c;该模块是Apache的一个代理模块&#xff0c;它允许你代理WebSocket请求。 以下是配置步骤的简要说明和示例&#xff1a; 确保你的Apache服务器安装了mod_…

【linux-1-Ubuntu常用命令-vim编辑器-Vscode链接ubuntu远程开发】

目录 1. 安装虚拟机Vmare和在虚拟机上安装Ubuntu系统&#xff1a;2. 常用的Ubuntu常识和常用命令2.1 文件系统结构2.2 常用命令2.3 vim编辑器 3. Ubuntu能联网但是ping不通电脑&#xff1a;4. Windows上安装VScode链接ubuntu系统&#xff0c;进行远程开发&#xff1a; 1. 安装虚…

变电站综合自动化系统:Modbus-PLC-645转IEC104网关方案

前言 电力行业作为关系国计民生的重要基础产业&#xff0c;是关系千家万户的公用事业。但是要做好电力行业安全保障工作的前提&#xff0c;是需要对应的技术人员详细了解电力工业使用的系统、设备以及各类协议的安全特性&#xff0c;本文将主要介绍IEC 104协议的定义和钡铼技术…

【百度Apollo】探索自动驾驶:Apollo 新版本 Beta 全新的Dreamview+,便捷灵活更丰富

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引入一、Dreamview介绍二、Dreamview 新特性2.1、基于模式的多场景——流程更简洁地图视角调节&#xff1a;调试流…

使用 scikit-learn 进行机器学习的基本原理-2

介绍 scikit-learn 估计器对象 每个算法都通过“Estimator”对象在 scikit-learn 中公开。 例如&#xff0c;线性回归是&#xff1a;sklearn.linear_model.LinearRegression 估计器参数&#xff1a;估计器的所有参数都可以在实例化时设置&#xff1a; 拟合数据 让我们用 nump…

智能体可靠性的革命性提升,揭秘知识工程领域的参考架构新篇章

引言&#xff1a;知识工程的演变与重要性 知识工程&#xff08;Knowledge Engineering&#xff0c;KE&#xff09;是一个涉及激发、捕获、概念化和形式化知识以用于信息系统的过程。自计算机科学和人工智能&#xff08;AI&#xff09;历史以来&#xff0c;知识工程的工作流程因…

【酱浦菌-爬虫技术细节】解决学术堂爬虫翻页(下一页)问题

首先我们通过css选择器获取页码信息&#xff0c;这里的css选择器&#xff0c;选择的是含有a标签的所有li标签&#xff0c;代码如下&#xff1a; li html_web.css(div.pd_c_xslb_left_fenye ul li>a) for li in li:li_url li.css(a::attr(href)).get()li_num li.css(a::t…

vue2迁移到vue3,v-model的调整

项目从vue2迁移到vue3&#xff0c;v-model不能再使用了&#xff0c;需要如何调整&#xff1f; 下面只提示变化最小的迁移&#xff0c;不赘述vue2和vue3中的常规写法。 vue2迁移到vue3&#xff0c;往往不想去调整之前的代码&#xff0c;以下就使用改动较小的方案进行调整。 I…

口袋实验室--使用AD2学习频谱参数测试

目录 1. 简介 2. 频谱相关参数 2.1 频谱相关基本概念 2.1.1 采样时间间隔 2.1.2 采样频率 2.1.3 采样点数 2.1.4 采样时间长度 2.1.5 谱线数 2.1.6 奈奎斯特频率 2.1.7 频谱分辨率 2.1.8 最高分析频率 2.1.9 频谱泄露 2.2 窗函数 2.2.1 AD2的窗函数 2.2.2 测试矩…

Python来计算 1,2,3,4 能组成多少个不相同且不重复的三位数?

我们今天的例子是 有 1&#xff0c;2&#xff0c;3&#xff0c;4 四个数字&#xff0c;它们能组成多省个互不相同且无重复的三位数&#xff1f;都分别是多少&#xff1f; 话不多说&#xff0c;我们先上代码 num 0 # 我们写了三个for循环&#xff0c;表示生成的三位数 for i…

【韩国】UE5的MetaHuman确实可以导入Blender进行编辑。

UE5的MetaHuman确实可以导入Blender进行编辑。根据网络上的信息&#xff0c;你可以将MetaHuman模型导出为FBX文件&#xff0c;然后在Blender中进行修改。修改完成后&#xff0c;你可以将其重新导入到Unreal Engine 5中4。请注意&#xff0c;当你在Blender中编辑模型时&#xff…

六天以太坊去中心化租房平台,前端+合约源码

六天以太坊去中心化租房平台 概述项目结构合约部署运行项目功能介绍一、首页二、房东后台我的房屋我的订单上架新房屋 三、租户后台我的房屋我的订单 四、仲裁后台 下载地址 概述 六天区块链房屋租赁系统&#xff0c;采用去中心化的方式实现了房屋的租赁功能。房东可在平台上托…

深度学习论文: MobileNetV4 - Universal Models for the Mobile Ecosystem及其PyTorch实现

深度学习论文: MobileNetV4 - Universal Models for the Mobile Ecosystem及其PyTorch实现 MobileNetV4 - Universal Models for the Mobile Ecosystem PDF: https://arxiv.org/pdf/2404.10518.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代码: ht…

视频怎么批量压缩?5个好用的电脑软件和在线网站

视频怎么批量压缩&#xff1f;有时候我们需要批量压缩视频来节省存储空间&#xff0c;便于管理文件和空间&#xff0c;快速的传输发送给他人。有些快捷的视频压缩工具却只支持单个视频导入&#xff0c;非常影响压缩效率&#xff0c;那么今天就向大家从软件和在线网站2个角度介绍…

Python 数据可视化 boxplot

Python 数据可视化 boxplot import pandas as pd import matplotlib.pyplot as plt import numpy as np import seaborn as sns# 读取 TSV 文件 df pd.read_csv(result.tsv, sep\t)normal_df df[df["sample_name"].str.contains("normal")] tumor_df df…