C语言 动态内存管理函数的 深度解析 #是不是对数组不能变大变小而烦恼呢?学会动态内存管理函数,消去数组耿直的烦恼#

news2025/1/19 12:56:48

文章目录

  • 前言
  • 为什么存在动态内存分配?
  • `malloc` 和 `free`
    • 1.malloc
    • 2.free
    • 3.使用
  • `calloc`
  • `realloc`
  • 常见的动态内存错误
    • 1.对NULL指针的解引用操作
    • 2.对动态开辟空间的越界访问
    • 3.对非动态开辟内存使用free释放
    • 4.使用free释放一块动态开辟内存的一部分
    • 5.对同一块动态内存多次释放
    • 6.动态开辟内存忘记释放(内存泄漏)
  • 写在最后

前言

  • 动态内存管理函数可以说很好用,但是有些小危险
  • 所谓动态内存分配,就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。 动态内存分配不像 数组 等 静态内存 分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。
  • 动态内存函数的头文件都是:<stdlib.h>

为什么存在动态内存分配?

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

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

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

  1. 空间开辟大小是固定的。
  2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。

此外:在后面的通讯录的完整实现,以及数据结构的完整实现大都是需要动态内存来实现的。

mallocfree

1.malloc

malloc是C语言提供的一个内存开辟函数,该函数的参数如下:

在这里插入图片描述

在这里插入图片描述

返回值:

在这里插入图片描述

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

malloc开辟的内存空间都是每有初始化的,观察内存如下:

在这里插入图片描述

2.free

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

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

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

注意:任何只要是开辟的动态内存空间(堆上的),都要free释放返还给操作系统。

3.使用

mallocfree是要共同使用的,有malloc开辟空间就一定要有free释放空间,通过上面的函数介绍,接下来结合使用。

例如,这里动态开辟一个能够存放10个整型的数组:

#include <stdio.h>
#include <stdlib.h> // 对应头文件

int main()
{
    // 因为返回的是void*,最好强转以下
	int* tmp = (int*)malloc(sizeof(int) * 10); // 也可以直接放一个40(要40字节)

	// 一定要检查开辟成功没有
	if (tmp == NULL)
	{
		perror("malloc fail");  // 这里打印错误“开辟失败”
		exit(-1);  // 这里可以理解为直接退出程序
	}

	// 开辟没问题,进行以下操作
	// 给开辟的数组赋值
	for (int i = 0; i < 10; ++i)
	{
		tmp[i] = i + 1;
	}

	// 打印
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", tmp[i]);
	}

	// 操作完后一定要释放空间
	// 传递指向那段空间起始位置的指针
	free(tmp);
	// 释放后要把该指针置为空,不然后面一不小心又使用该指针找到那块空间,属于非法访问了
	tmp = NULL;  

	return 0;
}

如果后面不释放,虽然现在的机器大都会自动返还给操作系统,但是出于严谨和安全,一定要记得free,不然会造成内存泄露问题,这是很严重的。

calloc

calloc也是动态内存分配函数
在这里插入图片描述

// 例如这里开辟一个有十个整型元素的数组
int* arr = (int*)calloc(10, sizeof(int));

在这里插入图片描述

在这里插入图片描述

通过上面的介绍,可以发现,calloc的功能与malloc几乎相同,其有两点不同之处:

  1. callocmalloc的函数参数不同;
  2. calloc开辟的空间会将全部元素初始化0,而malloc则是随机值。

如:

#include <stdio.h>
#include <stdlib.h> // 对应头文件

int main()
{
	//               个数     一个元素的大小
	int* tmp = calloc(10, sizeof(int)); 

	// 一定要检查开辟成功没有
	if (tmp == NULL)
	{
		perror("calloc fail");  // 这里打印错误“开辟失败”
		exit(-1);  // 这里可以理解为直接退出程序
	}

	// 开辟没问题,进行以下操作
	// 给开辟的数组赋值
	for (int i = 0; i < 10; ++i)
	{
		tmp[i] = i + 1;
	}

	// 打印
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", tmp[i]);
	}

	// 操作完后一定要释放空间
	// 传递指向那段空间起始位置的指针
	free(tmp);
	// 释放后要把该指针置为空,不然后面一不小心又使用该指针找到那块空间,属于非法访问了
	tmp = NULL;

	return 0;
}

在这里插入图片描述

所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

realloc

realloc函数的出现让动态内存管理更加灵活。

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

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

在这里插入图片描述

在这里插入图片描述

基础点:

  1. ptr 是要调整的内存地址。
  2. size 调整之后新大小。
  3. 返回值为调整之后的内存起始位置。
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
  • realloc在调整内存空间的是存在两种情况:

情况1: 内存中原有的空间之后有足够的空间来存放重新开辟的新大小的空间,这时直接在原有的空间之后追加空间。
情况2: 内存中原有的空间之后没有足够的空间来存放重新开辟的新大小的空间,这时在堆上另找一个合适大小的连续空间来使用。

在这里插入图片描述

那么我们如何来写代码呢?

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* tmp = (int*)malloc(100);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	//扩展容量
	//代码1
	tmp = (int*)realloc(tmp, 1000);//这样可以吗?(如果申请失败会如何?)

///
	
	//代码2
	int* p = realloc(tmp, 1000);
	if (p == NULL)
	{
		perror("realloc fail");
		exit(-1);
	}
	tmp = p;
	
	// 释放
	free(tmp);
	tmp = NULL;

	return 0;
}
  • 上面有两种写法,代码1跟代码2
  1. 分析代码1:如果重新开辟的空间开辟成功,并且是在原空间上做修改,那么这是可行的;如果原空间后面没有足够空间来开辟,另寻找到一份空间来存放,此时的地址空间的起始地址发生了改变,如果空间申请失败,而此时又将该空间的起始地址给了原有的指针变量tmp,这时原有空间就找不到了,并且会出现错误,所以还是不严谨的;
  2. 分析代码2:代码2是先将重新开辟的空间的起始地址交给一个临时变量,在判断这份空间的有效性,最后才赋值给原有的指针变量,这样做才是最安全且不会亏损原有空间的,所以,根据代码2的严谨性强的特点,以后realloc一定要写代码2这种样式。

有了reallocbuff的加持,我们想让数组变他就嘚变,哈哈哈

常见的动态内存错误

1.对NULL指针的解引用操作

void test()
{
	 int *p = (int *)malloc(INT_MAX/4);
	 // 这里没有判断是否开辟成功
	 *p = 20;  //如果p的值是NULL,就会有问题
	 free(p);
	 p = NULL;
}

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

void test()
{
	 int i = 0;
	 int *p = (int *)malloc(10*sizeof(int));
	 if(NULL == p)
	 {
	     exit(-1);
	 }
	 
	 for(i = 0; i <= 10; i++)
	 {
	     *(p+i) = i;//当i是10的时候越界访问
	 }
	 
	 free(p);
	 p = NULL;
}

i10就越界访问了,越界访问的后果就不用多说了把(哈哈哈哈哈,非法闯入)。

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

void test()
{
	 int a = 10;
	 int *p = &a;
	 free(p);  //ok?
	 p = NULL;
}

free是不能释放除动态开辟的内存以外的内存的,只适用于堆上。

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

void test()
{
	 int *p = (int *)malloc(100);
	 p++;
	 free(p);//p不再指向动态内存的起始位置
	 p = NULL;
}

free这样子释放相当于拦腰截断,会存在内存泄漏的问题。

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

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

对同一块空间多次释放,这当然是不行的。

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

这样是绝对不行的,内存泄漏迟早会吃光你的内存。

例如:

void test()
{
	 int *p = (int *)malloc(100);
	 if(NULL != p)
	 {
	 	*p = 20;
	 }
}
int main()
{
	 test();
	 // p指向的动态内存空间没有释放,虽然p变量销毁了,但申请的空间还在
	 return 0}

写在最后

动态内存分配是不是很容易就学会了,接下来就可以 ”肆无忌惮“ 的玩弄 ”数组“ 了,不过要小心内存泄漏噢!

感谢阅读本小白的博客,错误的地方请严厉指出噢!

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

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

相关文章

启动优化·基础论·浅析 Android 启动优化

“ 【小木箱成长营】启动优化系列文章(排期中)&#xff1a; 启动优化 工具论 启动优化常见的六种工具 启动优化 方法论 这样做启动优化时长降低 70% 启动优化 实战论 手把手教你破解启动优化十大难题 ”一、引言 Hello&#xff0c;我是小木箱&#xff0c;欢迎来到小木箱成…

Transformer——day63 读论文:SST:用于多标签图像识别的空间和语义变压器

SST&#xff1a;用于多标签图像识别的空间和语义变压器SST&#xff1a;用于多标签图像识别的空间和语义变压器I. INTRODUCTIONII. RELATED WORKA. Multi-Label Image RecognitionB. Transformer in Computer VisionIII. APPROACHA. MotivationB. Recap of TransformerC. Modeli…

【Java寒假打卡】Java基础-多线程

【Java寒假打卡】Java基础-多线程概述读线程实现方式-继承Thread多线程实现方式-Callable三种实现方式的对比Thread方法守护线程线程的优先级概述 并发和并行 进程和线程 读线程实现方式-继承Thread 继承Thread类进行实现 package com.hfut.edu.test11;public class MyThr…

2022年最新年终奖个人所得税计算计算方法及扣税标准

1、2021年12月30号&#xff0c;国务院决定年终奖等三项个税优惠续期&#xff1a;http://www.gov.cn/zhengce/2021-12/30/content_5665553.htm2、税务总局公告2019年第35号《关于非居民个人和无住所居民个人有关个人所得税政策的公告》&#xff1a;http://www.gov.cn/zhengce/zh…

一、Django项目创建

一. Python项目虚拟环境创建 在项目开发过程中会下载很多第三方库&#xff0c;有时不同项目对同一个库的依赖版本不同&#xff0c;如果所有项目都使用同一个python环境就会起冲突不便于管理。因此&#xff0c;实际开发中会为每一个项目都单独创建一个python的虚拟环境。这里的…

多线程~POSIX信号量实现生产者消费者模型,PV操作

目录 1.信号量的概念 2.sem_t信号量的操作函数 &#xff08;1&#xff09;.原理 &#xff08;2&#xff09;.sem_t函数的使用 &#xff08;3&#xff09;.基于信号量和环形队列的生产者消费者模型 1&#xff09;.大致实现思路 Task.hpp circular_queue.hpp circular_c…

基于java SSH框架的简单医疗管理系统源码+数据库,医疗管理系统基于springmvc+spring+hibernate

医疗管理系统 基于java SSH框架的简单医疗管理系统 环境说明 1、语言及开发环境&#xff1a; 语言实现说明JAVA后端用springmvcspringhibernate&#xff0c;前端使用htmlajax开发环境使用eclipse&#xff0c;maven管理。 数据库使用mysql&#xff1b; 完整代码下载地址&…

3D设计软件SolidWorks特征研究—— 3种放样方式 | 附视频教程

SolidWorks 是世界上第一个基于Windows开发的三维CAD系统&#xff0c;是可实现设计、模拟、成本估算、可制造性检查、CAM、可持续设计和数据管理等多种功能的三维设计软件&#xff0c;包含适用于钣金、焊件、曲面、模具、产品配置、DFM和CAM的专业工具&#xff0c;同时支持ECAD…

跑步耳机入耳式好还是半入耳式好、跑步用的耳机推荐

运动耳机一定是要跟佩戴舒适性、音质、性能关联在一起的&#xff0c;尤其是专业的运动耳机&#xff0c;还要具有久戴舒适运动时还不掉的特点&#xff0c;这个是我认为无论任何价价位的运动耳机都必须首要具备的条件&#xff0c;戴久了不舒服或者总掉&#xff0c;音质再好估计都…

带你了解防火墙

目录 1、什么是防火墙&#xff1f; 2、iptables 3、firewalld 如何实现端口转发&#xff1f; 1、什么是防火墙&#xff1f; 防火墙&#xff1a;防火墙是位于内部网和外部网之间的屏障&#xff0c;它按照系统管理员预先定义好的规则来控制数据包的进出。防火墙又可以分为硬件…

Error: Can‘t find Python executable “python“, you can set the PYTHON env var

亲测可用&#xff0c;若有疑问请私信 此问题&#xff0c;自己分析了好久才找到问题。其实有两种解决方案&#xff0c;我这里举例了一个&#xff0c;另一种环境变量配置也是可以的。希望能帮到大家。 问题描述&#xff1a; 在执行npm install 过程中出现 V未安装 解决方案&…

Python学习笔记-PyQt6工具栏

工具栏工具栏可以有多个&#xff0c;而且可以设置不同的位置参数。4.1工具栏位置参数QtCore.Qt.ToolBarArea.LeftToolBarAreaQtCore.Qt.ToolBarArea.RightToolBarAreaQtCore.Qt.ToolBarArea.TopToolBarAreaQtCore.Qt.ToolBarArea.BottomToolBarAreaQtCore.Qt.ToolBarArea.AllTo…

库的制作相关信息

库 通过把函数进行打包&#xff0c;然后形成相应的库&#xff0c;供其他的主函数使用。 静态库 以.a进行结尾&#xff0c;把库的东西&#xff08;头与库文件进行打包到之中&#xff09;打包到可执行程序之中。 静态库不是使用相对的位置信息&#xff0c;直接的信息。 bank…

如何通过Java导出带格式的 Excel 数据到 Word 表格

在Word中制作报表时&#xff0c;我们经常需要将Excel中的数据复制粘贴到Word中&#xff0c;这样则可以直接在Word文档中查看数据而无需打开另一个Excel文件。但是如果表格比较长&#xff0c;内容就会存在一定程度的丢失&#xff0c;无法完整显示数据。并且当工作量到达一定程度…

.net6 Web Api使用JWT-从后端到前端全部过程

jwt是做验证的必经之路&#xff0c;至于原理&#xff0c;就不在叙述了&#xff0c;可以参考官网 jwt官网介绍 JSON Web Tokens - jwt.io 原理介绍 JSON Web Token 入门教程 - 阮一峰的网络日志 看完之后&#xff0c;结合这个图&#xff0c;就明白了。 本案例使用vs2022&…

从技术专家到总经理,在不确定中探索和成长

你好&#xff0c;我是石东海。 前段时间我应邀跟一些企业做过一些交流&#xff0c;探讨在这个数字化时代&#xff0c;怎么去解决技术团队所面临的一些共性问题&#xff0c;包括技术思维转变和管理思维转变方面所经历的挑战。期间谈到了一些我个人的经历&#xff0c;以及这两年…

哈希表(一)—— 闭散列 / 开放地址法的模拟实现

哈希表的基本思路是通过某种方式将某个值映射到对应的位置&#xff0c;这里的采取的方式是除留余数法&#xff0c;即将原本的值取模以后再存入到数组的对应下标&#xff0c;即便存入的值是一个字符串&#xff0c;也可以根据字符串哈希算法将字符串转换成对应的ASCII码值&#x…

这家十年磨剑的企业级存储厂商,为什么将分布式块存储也开源了?

只要提到企业级存储&#xff0c;任何成功的厂商无不以十年为单位的积累&#xff0c;才能实现真正的创新。当然&#xff0c;作为存储领域相对更为复杂的分布式块存储&#xff0c;存储创新公司一般都不太愿意碰它。原因很简单&#xff0c;在技术自研的道路上&#xff0c;更需要坐…

Nginx之限流

文章目录Nginx如何限流配置基本的限流处理突发无延迟的排队高级配置示例location包含多limit_req指令配置相关功能发送到客户端的错误代码指定location拒绝所有请求总结流量限制(rate-limiting)&#xff0c;是 Nginx 中一个非常实用&#xff0c;却经常被错误理解和错误配置的功…

JavaScript 数据处理 · 基本统计(文末附视频)

第 5 节 基本数据处理 基本统计 学习了如何对 JavaScript 中的数组数据进行操作之后&#xff0c;我们就要回到刚开始选择购买这本小册的目的了&#xff1a;使用 JavaScript 开发灵活的数据应用。既然说是数据应用&#xff0c;那么便离不开统计计算&#xff0c;而数组就可以说…