编程之路,从0开始:动态内存管理

news2024/11/22 8:32:06

        Hello,大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路。

        我们今天来学习C语言中的动态内存管理。

 

目录

1、为什么要有动态内存管理?

2、malloc和free

(1)malloc函数

(2)free函数

3、calloc和realloc

(1)calloc函数

(2)realloc函数

4、常见的动态内存的错误

(1)对空指针的解引用问题

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

(3)对非动态开辟内存使用free

(4)使用free释放动态内存的一部分

(5)对于同一块动态内存多次释放

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

5、柔性数组


 

1、为什么要有动态内存管理?

        首先我们了解一下什么是动态内存管理。简单来说就是C语言支持你自己去开辟和释放内存。比方说我想要40字节的内存,那么我们就可以灵活的开辟这块内存,然后再在这块内存中存放数据。

那么我们直接用常规的方式存储数据不就好了吗?可以是可以,但是有两个缺点:

        1、空间开辟大小是固定的:比方说int他就是4个字节,没法改变。

        2、数组在声明的时候,必须指定数组的长度。数组空间大小确定了就不能调整了。

而动态内存管理的出现,就可以让程序员自己去开辟和释放内存,变得更灵活了。


2、malloc和free

(1)malloc函数

        malloc是一个动态内存开辟函数,其函数原型如下:2d301f9561504e2ab8dadb050a09f57a.png

        这个函数就是用来开辟空间的,其中malloc开辟的空间单位为字节。

如果开辟成功,就返回一个指向这块空间的指针。

如果开辟失败,就返回一个空指针。而null无法被使用。

        这就意味着,我们在开辟内存后一定要检查返回的是否为空指针。

        如果开辟成功,返回的指针类型为void*,所以我们在使用时要先强制转化该指针。

例如,我们现在开辟一块空间并使用它(放入内容):

#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(4);
	if (p != NULL)
	{
		*p = 5;
		printf("%d", *p);
	}
	free(p);
	p = NULL;
	return 0;
}

        以上是一套完整的开辟空间以及使用代码。大家应该都发现了里面出现了free,那么什么是free呢?


(2)free函数

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

其函数原型如下:

e9c872a1b602477293af7228ad6357cb.png

        如果参数ptr指向的空间不是动态内存,那么free函数的行为是未定义的。

        如果参数ptr指向空指针,那么函数什么事都不做,

        malloc和free函数的声明都在stdlib.h头文件中。

        注意:函数在申请内存之后一定要释放内存,否则可能会导致内存泄漏,或者由于重复开辟内存空间导致占用大量内存!


3、calloc和realloc

(1)calloc函数

函数原型:

aceba3c1a5be41e297f1af5368e03162.png

        它可以为num个占用size个字节的元素开辟空间。

        和malloc的区别是,calloc会把申请的空间全都初始化为0。

例如:

#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)calloc(5,4);
	if (p != NULL)
	{
		int i = 0;
		for (i = 0;i < 5;i++)
		{
			printf("%d ", *(p + i));
		}
	}
	free(p);
	p = NULL;
	return 0;
}

运行结果:

9a51b6899dad42b788de42b0140ca0cc.png


(2)realloc函数

        这个函数可以让我们调整内存。

函数原型:

8aab8334c95548bcaa7e3fcce40951a6.png

        我们先给定一个申请好动态内存的地址,在传入想到让其扩大后的最终大小。

        返回值为原内存起始位置。

        但需要注意的是,倘若我们无法调整到想要的空间大小,也就是说在原地址的基础上我们没有足够大的空间来扩充,那么该函数会找一个新的地址去开辟这么大的空间,在返回新开辟的位置的起始地址。

验证:

#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(1);//开辟1个字节
	if (p != NULL)
	{
		printf("%p\n", p);
		p = (int*)realloc(p, 2);//扩为两个字节
		printf("%p\n", p);
	}
	else
		return 1;
	free(p);
	p = NULL;
	return 0;
}

输出结果:

43d1ab002f31444baf28de48de02cc1e.png

测试代码2:

#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(4);//开辟4个字节
	if (p != NULL)
	{
		printf("%p\n", p);
		p = (int*)realloc(p, 16);//扩为16个字节
		printf("%p\n", p);
	}
	else
		return 1;
	free(p);
	p = NULL;
	return 0;
}

运行结果:

0e52a248657b4b88b18729cb055f2d71.png

        那么有没有一种可能就是说他在内存中根本找不到这么大的位置来放这块内存呢?

#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(4);//开辟4个字节
	if (p != NULL)
	{
		printf("%p\n", p);
		p = (int*)realloc(p, INT_MAX);//扩为最大整形数字
		printf("%p\n", p);
	}
	else
		return 1;//如果p为NULL直接结束程序。
	free(p);
	p = NULL;
	return 0;
}

运行结果(调整失败):
1e56e2d024e84bb4abe9d3c17a7e9878.png        所以说,在我们使用realloc是要注意一些。


4、常见的动态内存的错误

(1)对空指针的解引用问题

#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(INT_MAX);//开辟4个字节
	*p = 5;
	free(p);
	p = NULL;
	return 0;
}

我们无法开辟这个大小的内存空间,导致开辟失败返回空指针。程序直接挂掉


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

#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = NULL;
	p = (int*)malloc(5*sizeof(int));
	int i = 0;
	for (i = 0;i < 6;i++)
	{
		*(p + i) = 1;
		printf("%d", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

当i=5时越界访问,程序直接挂掉。


(3)对非动态开辟内存使用free

#include <stdio.h>
#include<stdlib.h>
int main()
{
	int* p = 5;
	printf("%d", *p);
	free(p);
	p = NULL;
	return 0;
}

没有输出结果。


(4)使用free释放动态内存的一部分

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

程序直接挂掉。


(5)对于同一块动态内存多次释放

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

程序直接挂掉。


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

        注意,这种情况可能会造成内存泄漏问题,当程序简单一些时,有可能可以正常运行,但是这不代表着可以不释放内存!开辟内存和释放内存一定是成套使用的。


5、柔性数组

        柔性数组就是一种可以让我们灵活调整其空间的数组。

但是需要注意:

        1、结构体中柔性数组成员前面至少包含一个其他成员

        2、sizeof返回这种结构的大小时不包含柔性数组

        3、用malloc调整结构中柔性数组的大小时,调整的最终大小应该大于这个结构体的大小。

现在我们使用以下柔性数组:

#include <stdio.h>
#include<stdlib.h>
struct s1
{
	int a;
	int arr[0];//柔性数组我们不填入他的大小
}s;
int main()
{
	struct s1 *p = (struct s1*)malloc(sizeof(struct s1) + 10 * sizeof(int));//结构体指针名称为p
	//开辟一块地址,大小为结构s所占字节加上100个整型大小(给柔性数组)
	if (p != NULL)//还记得吗?我们要判断是不是空指针
	{
		int i = 0;
		for (i = 0;i < 10;i++)
		{
			p->arr[i] = i;//访问结构体指针成员必须用结构体指针名称指向这个元素
			printf("%d ", p->arr[i]);
		}
		struct s1* p1 = (struct s1*)realloc(p, sizeof(struct s1) + 15 * sizeof(int));
		//判断是否为空指针(我们这里省略了)
		for (i = 10;i < 15;i++)
		{
			p1->arr[i] = i;//访问结构体指针成员必须用结构体指针名称指向这个元素
			printf("%d ", p1->arr[i]);
		}
		free(p);
		p = NULL;
		return 0;
	}
	else
		return 1;
}

        这是一套完整的使用并调整柔性数组大小的代码。

     


   那么我们现在思考一下,如果不用柔性数组,我们可以完成以上操作吗?

#include <stdio.h>
#include<stdlib.h>
struct s1
{
	int a;
	int *arr;
}s;
int main()
{
	struct s1 *p = (struct s1*)malloc(100 *sizeof(int));
	//这里我们先划定p这个结构体指针变量所在的那一块空间
	if (p != NULL)//还记得吗?我们要判断是不是空指针
	{
		p->arr = (int*)malloc(10 * sizeof(int));
		int i = 0;
		for (i = 0;i < 10;i++)
		{
			p->arr[i] = i;//下标引用操作符的实质是指针
			printf("%d ", p->arr[i]);
		}
		p->arr = (int*)realloc(p,15 * sizeof(int));//调整内存空间
		for (i = 10;i < 15;i++)
		{
			p->arr[i] = 1;//下标引用操作符的实质是指针
			printf("%d ", p->arr[i]);
		}
		free(p->arr);//我们开辟了两次内存,所以就要释放两次。
		p->arr = NULL;
		free(p);
		p = NULL;
		return 0;
	}
	else
		return 1;
}

尽管过程有些许的坎坷,但我们也是完成了这串代码。

以上两串代码的输出结果:

fdf300d53736443881a83e89dff43f49.png

        那么这两种代码写法哪一个更好的?

        答案是柔性数组更好。因为柔性数组方便内存释放,我们只用释放一次就好。

        其次,柔性数组更有利于访问速度,而且我们的代码写起来不也更简单吗?

好了,今天的内容就分享到这,觉得有帮助的老铁点点关注支持一下,我们下次再见!

 

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

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

相关文章

初始Python篇(4)—— 元组、字典

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; Python 目录 元组 相关概念 元组的创建与删除 元组的遍历 元组生成式 字典 相关概念 字典的创建与删除 字典的遍历与访问 字典…

d3-ease 的各种方法和示例

D3.js中的d3-ease模块提供了多种缓动函数&#xff0c;用于实现平滑的动画过渡效果。这些缓动函数通过扭曲时间控制动画中运动的方法&#xff0c;使得动画更加自然和流畅。以下是D3中常见的一些ease方法和示例代码&#xff1a; 线性缓动&#xff08;linear&#xff09;&#xff…

HTML5拖拽API学习 托拽排序和可托拽课程表

文章目录 前言拖拽API核心概念拖拽式使用流程例子注意事项综合例子&#x1f330; 可拖拽课程表拖拽排序 前言 前端拖拽功能让网页元素可以通过鼠标或触摸操作移动。HTML5 提供了标准的拖拽API&#xff0c;简化了拖放操作的实现。以下是拖拽API的基本使用指南&#xff1a; 拖拽…

Throwable、IO流、Java虚拟机

Error和Exception stream结尾都是字节流&#xff0c;reader和writer结尾都是字符流 两者的区别就是读写的时候一个是按字节读写&#xff0c;一个是按字符。 实际使用通常差不多。 在读写文件需要对内容按行处理&#xff0c;比如比较特定字符&#xff0c;处理某一行数据的时候一…

lanqiao OJ 364 跳石头

这个题目的条件是移动的石头数量给定&#xff0c;但是最小移动距离的最大值我们不知道&#xff0c;所以要通过mid来“猜测”。如果当前的mid需要移动的最小石头数量超过给定数&#xff0c;则mid不成立&#xff0c;需要缩小&#xff0c;反之则增大mid&#xff0c;直至找到一个最…

「一」HarmonyOS端云一体化概要

关于作者 白晓明 宁夏图尔科技有限公司董事长兼CEO、坚果派联合创始人 华为HDE、润和软件HiHope社区专家、鸿蒙KOL、仓颉KOL 华为开发者学堂/51CTO学堂/CSDN学堂认证讲师 开放原子开源基金会2023开源贡献之星 「目录」 「一」HarmonyOS端云一体化概要 「二」体验HarmonyOS端云一…

Shell基础(7)

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…

音视频pts/dts

现在的视频流有两个非常重要的时间戳&#xff0c;pts和dts&#xff0c;其中pts是显示的时候用&#xff0c;dts在解码的时候用。 pts很好理解&#xff0c;按照pts的顺序以及duration不间断的display就可以了。 dts在解码的时候用&#xff0c;那么这句话怎么理解&#xff0c;解…

sql server怎样用sql profiler捕获带变量值的慢sql

一 新建跟踪 点击工具-SQL Server Profiler&#xff1a; 点击文件-新建跟踪的按钮&#xff1a; 在‘事件选择’选项卡只选择如下两项内容&#xff08;RPC:Completed,SQL:BatchCompleted&#xff09;&#xff0c;把多余的取消勾选&#xff1a; 然后勾选上面截图中右下方的‘显示…

二叉树——输出叶子到根节点的路径

目录 代码 算法思想 例子 思维拓展 代码 int LeaveBit(Bitree T,int flag,int g) {if (!T) {return 0;}if (T->rchild NULL && T->lchild NULL) {//cout << "empty:" << T->data << endl;s.push(T->data);while (!s.emp…

深入理解Spring(三)

目录 2.1.3、Spring配置非自定义Bean 1)配置Druid数据源交由Spring管理 2)配置Connection交由Spring管理 3)配置日期对象交由Spring管理 4)配置MyBatis的SqlSessionFactory交由Spring管理 2.1.4、Bean实例化的基本流程 1)Bean信息定义对象-BeanDefinition 2)DefaultLi…

React Native 基础

React 的核心概念 定义函数式组件 import组件 要定义一个Cat组件,第一步要使用 import 语句来引入React以及React Native的 Text 组件: import React from react; import { Text } from react-native; 定义函数作为组件 const CatApp = () => {}; 渲染Text组件

SpringBoot,IOC,DI,分层解耦,统一响应

目录 详细参考day05 web请求 1、BS架构流程 2、RequestParam注解 完成参数名和形参的映射 3、controller接收json对象&#xff0c;使用RequestBody注解 4、PathVariable注解传递路径参数 5、ResponseBody&#xff08;return 响应数据&#xff09; RestController源码 6、统一响…

Linux:confluence8.5.9的部署(下载+安装+pojie)离线部署全流程 遇到的问题

原文地址Linux&#xff1a;confluence8.5.9的部署&#xff08;下载安装破ji&#xff09;离线部署全流程_atlassian-agent-v1.3.1.zip-CSDN博客 背景&#xff1a;个人使用2核4g 内存扛不住 总是卡住&#xff0c;但是流程通了所以 直接公司开服务器干生产 个人是centos7 公司…

线程池的实现与应用

一、线程池 一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用&#xff0c…

.net 8使用hangfire实现库存同步任务

C# 使用HangFire 第一章:.net Framework 4.6 WebAPI 使用Hangfire 第二章:net 8使用hangfire实现库存同步任务 文章目录 C# 使用HangFire前言项目源码一、项目架构二、项目服务介绍HangFire服务结构解析HangfireCollectionExtensions 类ModelHangfireSettingsHttpAuthInfoUs…

EventListener与EventBus

EventListener JDK JDK1.1开始就提供EventListener&#xff0c;一个标记接口&#xff0c;源码如下&#xff1a; /*** A tagging interface that all event listener interfaces must extend.*/ public interface EventListener { }JDK提供的java.util.EventObject&#xff1…

优先级队列PriorityQueue(堆)

1. 优先级队列 队列是一种先进先出的数据结构,而如果我们操作的数据带有优先级,我们出队的时候就会先出队优先级最高的元素.比如打游戏的时候有人给你打电话,操作系统看来有俩个进程,优先会处理打电话. 主要功能 1> 返回最高优先级对象 2> 添加新的对象 2. 堆的概念 2.1 …

【AI】人工智能报告解读——中国人工智能的发展

自 2016 年 AlphaGo 与世界顶级围棋选手对战后&#xff0c;AI 概念和技术从此走入大众视野。2017 年&#xff0c;国务院颁布《新一代人工智能发展规划》&#xff0c;这是中国在人工智能领域第一个部署文件&#xff0c;确定了人工智能产业发展的总体思路、战略目标和任务。技术和…

Flutter:photo_view图片预览功能

导入SDK photo_view: ^0.15.0单张图片预览&#xff0c;支持放大缩小 import package:flutter/material.dart; import package:photo_view/photo_view.dart;... ...class _MyHomePageState extends State<MyHomePage>{overrideWidget build(BuildContext context) {return…