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

动态内存开辟(上)

动态内存开辟(上)

  • 一. 为什么会存在动态内存分配
  • 二. 动态内存函数的介绍
    • 1. malloc和free
      • 1.1 malloc
      • 1.2 free
      • 1.3 完整的演示代码
    • 2. calloc
    • 3. realloc
    • 三. 常见的动态内存错误
      • 3.1 对NULL指针的解引用操作
      • 3.2 对动态开辟空间的越界访问
      • 3.3 对于非动态开辟内存使用free释放
      • 3.4 使用free释放一块动态开辟内存的一部分
      • 3.5 对同一块动态内存多次释放
      • 3.6 动态开辟内存忘记释放(内存泄漏)

一. 为什么会存在动态内存分配

我们已经掌握了一下的内存开辟方式

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

但是上述开辟空间的方式有两个特点

1 空间开辟的大小是固定的
2 数组在声明的时候 必须指定数组的长度 它所需要内存在编译时分配

特点1很好理解

就像上面 我们指定数组arr之后 在栈区上就不可以改变数组的大小了

但是我们使用malloc指定内存的大小之后 还是可以使用realloc来重置大小

特点2 我们使用一段代码来解释下

在这里插入图片描述

我们发现 动态开辟内存可以不指定数组的长度 而是传递进去一个变量

有时候对于空间大小的需求 我们只有在程序运行的时候才能知道

这个时候我们就只能使用动态内存开辟了

二. 动态内存函数的介绍

1. malloc和free

1.1 malloc

c语言提供了一个动态内存开辟的函数:

void* malloc (size_t size);

这个函数像内存申请一块连续可用的空间 并且返回指向这块空间的指针 是一个无符号类型的指针

这其中有四个注意点

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

特点一说明了这个函数的返回值

特点二说明了它开辟失败的情况

特点三详细介绍了它返回值的类型

特点四说明了如果开辟的空间为0 这个时候malloc会做什么

这里c语言提供了另一个函数 专门史用来做动态内存的释放和回收的

void free (void* ptr);

1.2 free

free函数也有两个特点

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

这里特别要注意特点1 我们来看一段代码

int main()
{
	int a = 10;
	int* p = &a;



	free(p);
	p = NULL;
	return 0;
}

如果我们写出来上面这一段代码 那么我们的编译器会给我们报错

在这里插入图片描述

特点二是一个知识点 我们记一下就好了

指针指向NULL的时候 free什么都不做

int main()
{
	int a = 10;
	int* p = &a;


	p = NULL;
	free(p);
	return 0;
}

在这里插入图片描述

1.3 完整的演示代码

int main()
{
	// 代码1
	int num = 0;
	scanf("%d", &num);
	int arr[50] = { 0 };

	//代码2
	int* p = (int*)malloc(num * sizeof(int));
	if (NULL == p)
	{
		perror(malloc);
	}
	else
	{
		int i = 0;
		for ( i = 0; i < num; i++)
		{
			*(p + i) = i;
		}
	}
	free(p);
	p = NULL;
	return 0;
}

2. calloc

c语言还提供了一个函数叫做 calloc calloc函数也用来动态内存分配

原型如下

void* calloc (size_t num, size_t size);

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

int main()
{
	int* p = (int*)calloc(40, sizeof(int));
	if (NULL==p)
	{
		perror(calloc);
	}
	free(p);
	p = NULL;
	return 0;
}

代码如上

我们来看看它们的内存

在这里插入图片描述

我们可以发现全部初始化内存确实为0

3. realloc

1 realloc函数的出现让动态内存管理更加灵活。
2 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整。

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

ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

在这里插入图片描述

情况一 如果说原有空间之后又足够大的空间 那么就直接扩展内存追加空间 原理啊空间的数据不发生变化

情况二 当情况2的时候 原有空间之后没有足够多的空间 扩展的方法是 在堆空间上另外寻找一个合适大小的连续空间来使用 这样函数返回的是一个新的内存地址

int main()
{
	int* p = (int*)malloc(100);
	if (p==NULL)
	{
		perror(malloc);
	}
	else
	{
		;
	}
	// p = (int*)realloc(p, 1000);
	// 这个写法因为有可能 realloc开辟内存失败 这样的话返回一个空指针
	// 开辟的内存空间就消失了 
	// 所以说 我们赋值前要判断是否为空指针
	int* ptr = (int*)realloc(p, 1000);
	if (ptr==NULL)
	{
		perror(realloc);
	}
	else
	{
		p = ptr;
	}

	free(p);
	p = NULL;
	return 0;
}

三. 常见的动态内存错误

3.1 对NULL指针的解引用操作

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//如果p的值是NULL,就会有问题 
	free(p);
}

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

int main()
{
	int* p = (int*)malloc(40);
	p++;
	free(p);
	return 0;
}

在这里插入图片描述

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

int main()
{
	int* p = (int*)malloc(40);
	free(p);
	free(p);
	return 0;
}

在这里插入图片描述

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

int main()
{
	while (1)
	{

		int* p = (int*)malloc(20);
		if (NULL == p)
		{
			perror(malloc);
		}
		else
		{
			*p = 20;
		}
	}
	return 0;
}

以上就是本篇博客的全部内容啦 由于博主才疏学浅 所以难免会出现纰漏 希望大佬们看到错误之后能够

不吝赐教 在评论区或者私信指正 博主一定及时修正

那么大家下期再见咯

相关文章:

  • 【云原生】阿里云容器镜像服务产品ACR EE之国内外场景应用模拟
  • html之网页结构
  • 手把手教你使用LabVIEW人工智能视觉工具包快速实现传统Opencv算子的调用(含源码)
  • Python小知识点
  • 目标检测 YOLOv5 - 最新版本v6.2模型在瑞芯微 Rockchip设备上运行的方案
  • Android 项目必备(三十)-->从 0 到 1 开发一个属于自己的 App
  • led灯珠型号及使用参数
  • MYSQL介绍——数据库的增删改及常用函数
  • 线性单功能PEG试剂甲氧基-聚乙二醇-丙烯酰胺,mPEG-Acrylamide,mPEG-ACA
  • 洛谷P3694
  • b站pink老师Echarts数据可视化笔记
  • 计算机三级数据库运行维护与优化(四)、合理使用索引、数据库存储结构和存取方法优化、完全规范化、索引的使用原则、、网络优化、监控内容、物化视图
  • HDMI/DVI____串行发送器
  • 深度操作系统 15.2——怀揣梦想,笃定前行
  • SAP PI PO 接口配置主体传播 RSXMB_CONFIG_PP_NEW
  • python3.6+scrapy+mysql 爬虫实战
  • 77. Combinations
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • DataBase in Android
  • Java 网络编程(2):UDP 的使用
  • Linux gpio口使用方法
  • ng6--错误信息小结(持续更新)
  • Python语法速览与机器学习开发环境搭建
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • SpiderData 2019年2月25日 DApp数据排行榜
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 构造函数(constructor)与原型链(prototype)关系
  • 简单易用的leetcode开发测试工具(npm)
  • 如何设计一个微型分布式架构?
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署
  • Spring第一个helloWorld
  • ​水经微图Web1.5.0版即将上线
  • #传输# #传输数据判断#
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • $L^p$ 调和函数恒为零
  • (04)odoo视图操作
  • (arch)linux 转换文件编码格式
  • (Java)【深基9.例1】选举学生会
  • (第一天)包装对象、作用域、创建对象
  • (接口封装)
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (三)mysql_MYSQL(三)
  • (十六)串口UART
  • (四)linux文件内容查看
  • (转)scrum常见工具列表
  • (转)关于多人操作数据的处理策略
  • (转载)(官方)UE4--图像编程----着色器开发
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .net实现客户区延伸至至非客户区
  • .NET微信公众号开发-2.0创建自定义菜单