C语言【动态内存】

news2025/1/12 8:52:37

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.coloradmin.cn/o/1635670.html

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

相关文章

上位机图像处理和嵌入式模块部署(树莓派4b利用驱动实现进程数据共享)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们讨论过&#xff0c;目前在linux系统上面有很多办法可以实现多进程数据共享。这里面比如说管道&#xff0c;比如说共享内存&#xff0c;比如…

Electron开发 umi react 应用

Electron 是一个跨平台桌面端的应用框架&#xff0c;electron 底层依赖3 个核心组件 Chromium、Node.js、Electron API&#xff0c;Chromium 是 Chrome 的开源版本&#xff0c;Node.js可以编写后台应用程序&#xff0c;集成 Node.js 到 Electron&#xff0c;使得 Electron 可以…

21.哀家要长脑子了!

1.21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 我做过啊&#xff0c;为什么还是不能独立做出来&#xff0c;为什么为什么啊啊啊 嘻嘻奔向五一 是这样的&#xff1a; 要按升序连接&#xff0c;以链表2头结点作为开端&#xff0c;哪个小就先连接哪个&#xff…

75、堆-前K个高频元素

思路 这道题还是使用优先队列&#xff0c;是要大根堆&#xff0c;然后创建一个类&#xff0c;成员变量值和次数。大根堆基于次数排序。前k个就拿出前k的类的值即可。代码如下&#xff1a; class Solution {public int[] topKFrequent(int[] nums, int k) {if (nums null || …

半导体机台文件导出,如何实现统一管理减轻运维压力?

半导体机台在半导体制造过程中会产生多种数据&#xff0c;这些数据对于设备的运行、监控、优化和故障诊断等方面都具有重要意义。以下是半导体机台可能产生的一些主要数据类型&#xff1a; 工艺控制数据&#xff1a;这包括在制造过程中的各个工艺步骤&#xff08;如光刻、蚀刻、…

垃圾中转站远程监控运维管理系统解决方案

在城市环卫体系中&#xff0c;垃圾中转站作为连接居民区与末端处理设施的关键节点&#xff0c;其高效稳定运行对于保障城市环境清洁、推动垃圾分类与资源化利用具有重要意义。然而&#xff0c;传统的管理模式往往存在实时监控不足、运维效率低下等问题。 一、设计理念 1. 实时…

【派兹互连·SailWind】美国瞄准“小华为”

有“小华为”之称的海能达遭遇了来自美国方面的压力。 近日&#xff0c;海能达紧急发公告称&#xff0c;公司收到美国法院的判令&#xff0c;临时被禁止在全球范围内销售双向无线电技术的产品&#xff0c;并处以每天100万美元的罚款&#xff0c;直至公司完全遵守禁诉令之时止。…

Go Web开发【xorm 框架】

1、xorm 1.1、xorm 简介 xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 特性 支持 struct 和数据库表之间的灵活映射&#xff0c;并支持自动同步事务支持同时支持原始SQL语句和ORM操作的混合执行使用连写来简化调用支持使用ID, In, Where, Limit,…

C语言:选择结构

选择结构 1.if 语句1.1.if1.2.if-else1.3.if的嵌套举例示例结果分析 举例示例结果 2.switch语句举例示例结果分析 C语言中的选择结构用于根据特定条件执行不同的代码段。选择结构是C语言编程中实现程序逻辑流程控制的基础&#xff0c;它们能够使程序根据不同的输入或条件执行不…

《霍格沃茨之遗》找不到emp.dll如何修复?分享5种亲测有效的方法

在我们享受电脑游戏带来的乐趣时&#xff0c;偶尔会遇到一些技术上问题&#xff0c;具体来说&#xff0c;当你启动一款游戏&#xff0c;系统却弹出一个提示“由于找不到emp.dll文件&#xff0c;因此无法继续执行代码”&#xff0c;这样的情况确实让人感到扫兴。这究竟是什么原因…

【五一特惠活动】FME视频教程限时回馈大优惠

目录 一、FME视频教程五一限定套餐 二、购买方式 为感谢各位粉丝的持续支持&#xff0c;让大家学好用好FME&#xff0c;轻松工作、少加班&#xff0c;特推出FME视频教程五一限定套餐&#xff0c;套餐包括FME入门视频教程、FME进阶视频教程、FME案例实战教程&#xff0c;总共三…

Objenesis 底层探究

Objenesis 简介 Objenesis 是一个 Java 库&#xff0c;用于在不调用构造方法的情况下创建对象。由于绕过了构造方法&#xff0c;所以无法调用构造方法中的初始化逻辑。相应的&#xff0c;Objenesis 无法创建抽象类、枚举、接口的实例对象。 起源 与其称之为起源&#xff0c;…

特斯拉与百度合作;字节正全力追赶AI业务;小红书内测自研大模型

特斯拉中国版 FSD 或与百度合作 根据彭博社的报道&#xff0c;特斯拉将通过于百度公司达成地图和导航协议&#xff0c;扫清在中国推出 FSD 功能的关键障碍。 此前&#xff0c;中国汽车工业协会、国家计算机网络应急技术处理协调中心发布《关于汽车数据处理 4 项安全要求检测情…

ThingsBoard PE专业版解决方案技术文档——温度湿度

1、项目总览 2、设备接入 3、设备告警 3.1 高温告警 创建一个Flag作为标杆,作为开启告警的开关。 3.2 低湿度告警

【STM32】快速使用F407通用定时器输出可变PWM

网上的文章太啰嗦&#xff0c;这里直接开始。 使用的是STM32CubeIDE&#xff0c;HAL。以通用定时器TIM12在 通道2上输出1KHz的PWM为例。 要确定输出的引脚、定时器连接在哪里。 TIM2、3、4、5、12、13、14在APB1上&#xff0c;最大计数频率84M。 TIM1、8、9、10、11在APB2…

【Unity动画系统】动画状态转换详解

动画状态转换 此空处可以改换新转换名字。 表示有多个转换&#xff0c;播放顺序不可调整。 Solo:表示只执行它们&#xff0c;其他没勾选的不考虑&#xff1b;都勾选了&#xff0c;哪个转换条件先满足&#xff0c;就先执行哪个转换;如果同时满足&#xff0c;那就按顺序执行。 M…

【笔试训练】day15

1.平方数 水题直接看代码 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include<math.h> #include<algorithm> using namespace std; typedef long long ll; int main() {ll x;cin >> x;ll a sqrt(x);if (abs(a * a -…

新冠轻症康复者病毒仍在复制 新冠抗病毒药先诺欣和乐睿灵怎么选?

近日,国家疾控局发布的通知显示,当前我国新冠疫情持续保持低水平波浪式流行态势,但新冠病毒仍在变异,疫情仍存在一定的反复性。“五一”假期人员流动性上升和聚集性活动增加,可能加大疫情传播风险,防控任务艰巨复杂。 新冠病毒该怎么防?感染新冠后又当如何用药?对新冠病毒的…

C语言指针和数组的一些笔试题

文章目录 前言一、一维数组二、字符数组-1三、字符数组-2总结 前言 C语言指针和数组的一些笔试题 一、一维数组 #include <stdio.h> int main() {int a[] { 1,2,3,4 };printf("%d\n", sizeof(a));printf("%d\n", sizeof(a 0));printf("%d\n…

LLM之RAG理论(十一)| 面向生产的RAG应用程序的12种调整策略指南

本文对文本RAG涉及到的主要12种关键“超参数”进行简单总结&#xff0c;主要包括摄取阶段&#xff08;数据清洗、数据分块、embedding模型选择、元数据过滤、多重索引和索引算法&#xff09;和推理阶段【检索和生成】&#xff08;查询转换、检索参数、高级检索策略、重排序、大…