【进阶C语言】动态内存管理

news2024/9/22 23:34:55

在这里插入图片描述
前言

📕作者简介:热爱跑步的恒川,致力于C/C++、Java、Python等多编程语言,热爱跑步,喜爱音乐的一位博主。
📗本文收录于C语言进阶系列,本专栏主要内容为数据的存储、指针的进阶、字符串和内存函数的介绍、自定义类型结构、动态内存管理、文件操作等,持续更新!
📘相关专栏Python,Java等正在发展,拭目以待!


动态内存管理

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

1. 为什么存在动态内存分配

我们已经掌握的内存开辟方式有:

int val = 20;//在栈空间上开辟四个字节

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

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

  1. 空间开辟大小是固定的。

  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。
这时候就只能试试动态存开辟了。

2. 动态内存函数的介绍

2.1 malloc和free

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

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

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

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

void free (void* ptr);

free函数用来释放动态开辟的内存。

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

malloc和free都声明在 stdlib.h 头文件中。
举个例子:

#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;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = i + 1;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;

	return 0;
}

代码结果:

1,2,3,4,5

那我们试一试直接打印开辟的动态空间,看看里面的内容是什么?

int main()
{
	//张三
	//申请
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;

	return 0;
}

代码结果:

-842150451 -842150451 -842150451 -842150451 -842150451

发现malloc开辟的动态空间打印的是随机值

2.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(10, sizeof(int));
	if (p == NULL)
	{
		printf("calloc()-->%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}

	//释放
	free(p);
	p = NULL;
	return 0;
}

代码结果:

0 0 0 0 0 0 0 0 0 0

发现calloc开辟的动态空间打印的是0。
calloc和malloc的对比:

  1. 参数都是不一样的
  2. 都是在堆区上申请的内存空间,但是malloc不初始化,calloc会初始化为0
    如果要初始化,就使用calloc
    不需要初始化,就可以使用malloc

2.3 realloc

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

函数原型如下:

void* realloc (void* ptr, size_t size);
  • ptr 是要调整的内存地址
  • size 调整之后新大小
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
  • realloc在调整内存空间的是存在两种情况:
    情况1:原有空间之后有足够大的空间
    情况2:原有空间之后没有足够大的空间
    在这里插入图片描述

情况1

当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2

当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些。
举个例子:


int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = i + 1;
	}
	int* ptr = (int*)realloc(p, 400000);

	if (ptr != NULL)
	{
		p = ptr;
		//使用
		for (i = 5; i < 10; i++)
		{
			p[i] = i + 1;
		}
		for (i = 0; i < 10; i++)
		{
			printf("%d ", p[i]);
		}
	}
	
	//释放
	free(p);
	p = NULL;

	return 0;
}

方案一:realloc函数返回的是旧地址
在这里插入图片描述
方案二:realloc函数返回的是新地址

在这里插入图片描述

  1. realloc会找更大的空间
  2. 将原来的数据拷贝到新的空间
  3. 释放旧的空间
  4. 返回新空间的地址

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

int main()
{
	int* p = (int*)malloc(20);
	//可能会出现对NULL指针的解引用操作
	//所以malloc函数的返回值要判断的
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = i;
	}
	free(p);
	p = NULL;

	return 0;
}

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

int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//可能会出现对NULL指针的解引用操作
	//所以malloc函数的返回值要判断的
	int i = 0;
	//越界访问
	for (i = 0; i < 10; i++)
	{
		p[i] = i;
	}
	free(p);
	p = NULL;

	return 0;
}

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

//对非动态开辟内存使用free释放
int main()
{
	int arr[10] = { 1,2,3,4,5 };
	int* p = arr;
	//....
	free(p);
	p = NULL;

	return 0;
}

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

//使用free释放一块动态开辟内存的一部分
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}

	int i = 0;
	//[1] [2] [3] [4] [5] [ ] [ ] [ ] [ ] [ ] 
	for (i = 0; i < 5; i++)
	{
		*p = i + 1;
		p++;  //这种写法不可取,如果想要释放整个空间,必须将p放在起始位置上才可以
	}
	//释放 
	free(p);
	p = NULL;

	return 0;
}

*p = i + 1;
p++; //这种写法不可取,如果想要释放整个空间,必须将p放在起始位置上才可以,不然程序会崩溃掉

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

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放

}

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

//一直在吃内存,内存不释放
void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}

int main()
{
	test();
	while (1);
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏。
切记:
动态开辟的空间一定要释放,并且正确释放 。
提示:
malloc,calloc,realloc,所申请的空间,如果不想使用,需要free释放
如果不使用free释放:程序结束之后,也会由操作系统回收!
如果不使用free释放,程序也不结束,内存就会泄露。
工作时:
自己申请的,尽量自己释放
自己不释放的,告诉别人来释放
这样就可以避免动态内存泄漏的问题

如果这份博客对大家有帮助,希望各位给恒川一个免费的点赞👍作为鼓励,并评论收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给恒川的意见,欢迎评论区留言。

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

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

相关文章

win10 专业版登录Microsoft账户提示:0x800704cf 错误代码——问题解决记录

win10 专业版登录Microsoft账户提示&#xff1a;0x800704cf 错误代码——问题解决记录 系统版本 版本 Windows 10 专业版 版本号 21H2 安装日期 ‎2021/‎5/‎7 操作系统内部版本 19044.2846 体验 Windows Feature Experience Pack 120.2212.4190.0 问题描述 曾经手动修改过…

《程序员面试金典(第6版)》面试题 10.11. 峰与谷(双指针,贪心思想)

题目描述 在一个整数数组中&#xff0c;“峰”是大于或等于相邻整数的元素&#xff0c;相应地&#xff0c;“谷”是小于或等于相邻整数的元素。例如&#xff0c;在数组{5, 8, 4, 2, 3, 4, 6}中&#xff0c;{8, 6}是峰&#xff0c; {5, 2}是谷。现在给定一个整数数组&#xff0c…

网络原理IP协议

hi,大家好,小魏又来了,我们已经认识了UDP,TCP,现在来认识一下位于网络层的协议,IP 认识IP地址 1.地址管理 2.路由选择 在之前的讲解中我们已经认识到了网络层的IP协议,负责寻路操作 IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地址&#xff0…

【Java版oj】day35年会抽奖、抄送列表

目录 一、年会抽奖 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、抄送列表 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 一、年会抽奖 …

C++ 命名空间 输入输出 缺省参数 引用 函数重载

在学习C之前&#xff0c;我们要先知道C和C是向上兼容的&#xff0c;也就是说&#xff0c;在cpp文件中既可以写入C的代码&#xff0c;也可以写C的代码&#xff0c;在日常编写代码中&#xff0c;经常会出现C和C混编的情况。 此博客都是在 C 的缺陷的基础之上 整理 C 中对其的优化…

PyTorch 之 强大的 hub 模块和搭建神经网络进行气温预测

文章目录一、强大的 hub 模块1. hub 模块的使用2. hub 模块的代码演示二、搭建神经网络进行气温预测1. 数据信息处理2. 数据图画绘制3. 构建网络模型4. 更简单的构建网络模型本文参加新星计划人工智能(Pytorch)赛道&#xff1a;https://bbs.csdn.net/topics/613989052 一、强…

机器学习——回归与聚类算法

线性回归 广义的线性模型 不仅是自变量是一次方的是线性模型&#xff0c;参数是一次方的也是线性模型&#xff0c;比如&#xff1a; 总结&#xff1a;线性关系的一定是线性模型&#xff0c;线性模型的不一定是线性关系。 损失函数 优化损失 求解模型中的w&#xff0c;使得…

SeNet论文解读/总结

此文章为深度学习在计算机视觉领域的图片分类经典论文SeNet&#xff08;Squeeze-and-Excitation Networks&#xff09;论文总结。 此系列文章是非常适合深度学习领域的小白观看的图像分类经典论文。系列文章如下&#xff1a; AlexNet&#xff1a;AlexNet论文解读/总结_alexnet…

uniapp-搜索配置

自定义搜索组件 1.定义组件的 UI 结构: <template><!-- 通过属性绑定的形式&#xff0c;为 .my-search-container 盒子和 .my-search-box 盒子动态绑定 style 属性 --><view class"my-search-container" :style"{background-color: bgcolor}&q…

《花雕学AI》23:中文调教ChatGPT的秘诀:体验测试与通用案例,解锁无限有趣玩法!

引言&#xff1a; 你有没有想过和一台智能机器人聊天&#xff1f;你有没有想过让一台智能机器人为你创作诗歌、故事或歌曲&#xff1f;你有没有想过让一台智能机器人陪你玩游戏、学习或社交&#xff1f;如果你的答案是肯定的&#xff0c;那么你一定会对ChatGPT感兴趣。 ChatG…

Hystrix详解

前言 Hystrix基于Feign&#xff0c;想熟悉Hystrix&#xff0c;必须先熟悉Feign。 Feign&#xff08;简介和使用&#xff09;&#xff1a; Feign&#xff08;简介和使用&#xff09;_长头发的程序猿的博客-CSDN博客 Hystrix简介 hystrix对应的中文名字是“豪猪”&#xff0c…

Android开发 Camera2获取输出SurfaceTexture

目录 一、Camera2概述 1 Pipeline 2 CameraManager 3 CameraDevice 4 CameraCharacteristics 5 CameraCaptureSession 6 CaptureRequest 7 Surface 8 CaptureResult 三、Camera2的特性 1 Camera2 才支持的高级特性 2 Camera1 迁移到 Camera2 二、示例源码 一、Came…

Honggfuzz Linux arch_clone 源码阅读 (setjmp, clone)

Honggfuzz Linux arch_clone 源码阅读 &#xff08;setjmp, clone&#xff09; 阅读 Honggfuzz 系统架构相关源码&#xff0c;在创建子进程部分遇到了几个问题&#xff0c;经过研究得以解决&#xff0c;在此记录。 Source Code 代码节选自linux/arch.c&#xff0c;已添加注释&…

RabbitMq 消息可靠性问题(一) --- publisher发送时丢失

前言 消息从生产者发送到exchange, 再到 queue, 再到消费者。这个过程中有哪些有消息丢失的可能性呢&#xff1f; 发送时丢失&#xff1a; 生产者发送的消息未送达 exchange消息到达 exchange 后未到达 queue MQ 宕机&#xff0c;queue将消息丢失consumer 接收到消息后未消费…

聊聊如何运用JAVA注解处理器(APT)

什么是APT APT&#xff08;Annotation Processing Tool&#xff09;它是Java编译期注解处理器&#xff0c;它可以让开发人员在编译期对注解进行处理&#xff0c;通过APT可以获取到注解和被注解对象的相关信息&#xff0c;并根据这些信息在编译期按我们的需求生成java代码模板或…

基于DistFlow的含分布式电源配电网优化模型【IEEE39节点】(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

SpringBoot【基础篇】---- SSMP整合综合案例

SpringBoot【基础篇】---- SSMP整合综合案例1. 模块创建2. 实体类开发3. 数据层开发----基于CRUD查看MP运行日志查看 MP 的运行日志4. 数据层开发----分页功能制作5. 数据层开发----条件查询功能制作6. 业务层开发业务层快速开发7. 表现层开发8. 表现层消息一致性处理9. 前后端…

STC32G单片机内置ADC及应用编程

一 STC32G单片机内置ADC模块简介 STC32G单片机内部集成了一个12位高速ADC转换器&#xff0c;ADC的最高时钟频率为系统频率的1/2。其输入通道多达15个&#xff08;第15通道为专门测量内部1.19V参考信号源的通道&#xff09;&#xff0c;可分时切换使用。 STC15系列单片机内置AD…

AES加密

来源&#xff1a;链接: b站up主可厉害的土豆 &#xff08;据评论区说图片中有计算错误&#xff0c;但是过程是对的。只是了解过程问题不大&#xff0c;专门研究这一块的话自己可以看视频算一下&#xff09; 准备 首先&#xff0c;明文是固定长度 16字节 128位。 密钥长度可以…

C++语法(18)---- set和map

C语法&#xff08;17&#xff09;---- 二叉搜索树_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130174864 目录 1.set的介绍 1.set使用 1.基本结构 2.insert 3.erase 4.find 5.count 2.multiset 1.count 2.find 2.map的介绍 1.map …