动态内存空间管理

news2024/11/18 23:22:32

在这里插入图片描述

  • 欢迎来到我的 世界 ^ _ ^
  • 希望作者的文章对你有所帮助,有不足的地方还请指正,大家一起学习交流 !

文章目录

  • 前言:
  • 动态内存是什么
  • 一、动态内存介绍:
    • 动态内存有关函数介绍
      • 1.malloc和free
      • 2.calloc函数
      • 3.realloc函数
  • 二、一些常见的动态内存错误:
    • 1.对NULL指针解引用操作:
    • 2.对动态开辟空间的越界访问
      • 2.1. 越界访问的后果
    • 3.对非动态开辟的内存使用free释放
    • 4.使用free释放一块动态开辟内存的一部分
    • 5.对一块动态空间的多次释放
    • 6.忘记释放动态内存空间(内存泄漏)
  • 完结:

前言:

前面发出一篇: C语言结构体篇,对我自己而言,还有很多地方只在了片面之上,但是收获还是很大的,比如说:为了知道结构体的嵌套又或是内存对齐,我翻阅了很多资料,这里感谢:买酒的小码农 博主满满的干货呀。
也证明了我学的知识也是不够扎实,老铁们也可以在评论区和我进行交流哦,咱们要一起进步呀。写一篇博客你会发现你许多的不足,这也是对我自己的再一次学习,对此我想告诉大家我很喜欢的一句话:
———————对过程全力以赴,对结果淡然处之

动态内存是什么

  1. 在C语言中我们的内存开辟是在栈上进行的,简而言之这属于静态内存:
int i=0;//这是向内存开辟了一块4个字节的空间来存放了变量 i
int arr[10]={0};//这是开辟了40个字节来存放数组 arr
//      在这里开辟的内存空间大小是不可变的;
//这里有人想问:我为什么想着改变着的内存空间呢?
//   你想啊,这里我们开辟了一块40个字节的数组,这么大的一块空间可不是想变大就大,
//想小就小的,这可能会和我们所需要的不一样,这会很不方便
//但是如果有一个你想多大就多大的空间呢?
//所以这就是我下来要讲的 动态内存空间管理 了

在C语言中我们的静态内存开辟,这样做的特点:
1.他所开辟的空间是固定的
2.数组在声明的时候,必须指定数组的长度,他所需要的内存在编译的时候分配

但对于空间的需求,我们有的时候并不知道,有可能空间开大了造成了浪费,也有可能空间开小了造成栈溢出,这样我们就需要一个动态的内存管理让我们需要多少内存的时候开辟多少。

但也不是动态内存一定静态内存开辟空间好,任何好东西都是一把双刃剑!!记住这句话!!!


一、动态内存介绍:

动态内存开辟空间是在堆区,和静态内存开辟不同,动态内存开辟需要调用函数:

动态内存有关函数介绍

1.malloc和free

   void*  malloc (size_t  size);        

这个函数可以在内存中申请一块连续的空间(堆区),并且返回这块空间的起始位置的地址,但是不会初始化这块空间。

如:
      int* pf= (int *) malloc(40);  //  40 ---->的单位是字节

开辟了一块40个字节大小的空间,但是又不知道是什么类型的,就需要你手动强制类型转换你需要的类型(int*)。

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

这里因为我们有可能会动态内存申请失败的,所以我们应该更加加以检查,养成好习惯,在每次开辟了一块动态内存空间后面立马进行判断是否申请开辟空间成功。

  • C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的:
  void * free (void * ptr); 

他是专门用来释放动态内存开辟的内存的;

注意:

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

1.当malloc函数申请的内存空间,当程序退出时,会还给操作系统;当程序不退出时,动态内存申请的内存空间,不会主动释放。一定需要free函数来释放,所以我们要养成好习惯,每每开辟一块动态内存空间,后面一定要记得free释放这块空间。
2.free只释放动态内存空间,不能对静态内存操作哦
3.free释放该指针的地址后,会导致该指针为野指针,一定给该指针重新赋值空指针(NULL)

实例:

int main()
{
	//开辟一块40个字节的动态内存空间,且强转位int*类型
	int* pf =(int *) malloc(40);
	//判断是否开辟成功
	if (pf == NULL)
	{
		perror("malloc");//如果开辟失败了,打印出原因
		return 1;      //并且不用再继续往下了,就返回1
	}
	//如果开辟成功,则打印出这个地址所指的内容和往后10个的内容
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", *(pf + i));
	}

	//释放开辟的内存
	free(pf);//这里注意,释放pf的地址后,pf就会变成一个野指针,
	//这里一定需要重新给他赋值上NULL,这也是个好习惯
	pf = NULL;

	return 0;
}

在这里插入图片描述

这图也证明了,当malloc开辟的动态内存,不会给他进行初始化;

2.calloc函数

  • 这个函数几乎是和 malloc 函数类似,也是用来动态内存分配:
   void*  calloc  (size_t  num,size_t  size);
  • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

实例:几乎和上述一样的代码;

int main()
{
	//开辟一块40个字节的动态内存空间,且强转位int*类型
	int* pf = (int*)calloc(10, sizeof(int ));
	//判断是否开辟成功
	if (pf == NULL)
	{
		perror("calloc");//如果开辟失败了,打印出原因
		return 1;      //并且不用再继续往下了,就返回1
	}
	//如果开辟成功,则打印出这个地址所指的内容和往后10个的内容
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", *(pf + i));
	}

	//释放开辟的内存
	free(pf);//这里注意,释放pf的地址后,pf就会变成一个野指针,
	//这里一定需要重新给他赋值上NULL,这也是个好习惯
	pf = NULL;

	return 0;
}

在这里插入图片描述

和上述一样的代码,但是运行结果是全为0,就可以证明 calloc 开辟的动态内存是会给他初始化为0的。

3.realloc函数

  • realloc函数让动态内存管理更加的灵活;
  • 有时候发现申请的空间不合适,需要对内存大小进行灵活的调整,那么realloc函数就可以做到对动态开辟内存大小的调整;
 void * realloc ( void* ptr , size_t  size);

其参数:
1.ptr是要调整的内存地址
2.size是调整之后的新大小
3.返回值为调整之后的内存起始位置
4.这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
5.realloc在调整内存空间有两种情况:
—————— (1).在原开辟空间后增加一块空间时:发现后面没有足够的空间,+图解:

在这里插入图片描述
1.开辟新空间 2.将旧的空间中的数据拷贝到新空间 3.释放旧的空间 4.返回新空间的起始位置地址

——————(2).若后面有足够的空间,则后面加上(size-原来的字节个字节)的空间大小。+图解:

在这里插入图片描述
直接在后面加上一块空间;

若想要使用realloc函数,首先需要一块已经开辟的动态内存空间,这样才能用realloc调整。
实现:

int main()
{
	//开辟一块40个字节的整形空间大小
	int* pc = (int*)malloc(40);
	if (pc == NULL)
	{
		perror("malloc");
		return 1;
	}
	//初始化 1 - 10 的数字
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		pc[i] = i + 1;
	}
	//想增加一些空间
	int* ptr = (int*)realloc(pc, 80);
	//     这里我想特别强调一点,为什么不让 pc指针接收,
	//而要创造一个新的指针接收呢?
	//     因为realloc增容可能会失败,那返回的就是NUKLL,
	//而如果把 NULL 给到 pc指针,那会造成内存泄露
	//    当然内存泄露是一个动态内存错误,再这篇博客的后面 
	//会一一介绍到动态内存错误
	//增加空间失败
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	//增加空间成功
	pc = ptr;//是将返回来的地址交给原来的指针维护
	ptr = NULL;

	//打印数据
	for (i = 0; i < 20; i++)
	{
		printf("%d\n", pc[i]);
	}

	//释放
	free(pc);
	pc = NULL;

	return 0;
}

在这里插入图片描述
前面的1到10的初始化,后面是新增加的10个字节大小空间,并没有进行初始化。

- 如果是想要减少空间,那就更简单了:

减少空间不存在上述的的情况,可以直接将realloc后参数的size改成你想要的。
如:将原来的空间大小减少至20个字节的大小。

int *ptr = (int *) realloc(pc,20);

二、一些常见的动态内存错误:

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

这个问题很容易理解,上面有应该也是提到过:
就是在 malloc 开辟动态内存空间时,一定记得要对返回来的地址进行检查,因为malloc并不一定会开辟空间成功的,如果失败了,就会返回一个空指针(NULL),而如果后续需要对该指针进行解引用,会出现问题;

int main()
{
    int *ptr;
    ptr=(int*)malloc(sizeof(int));
    //这里不进行判断,如果时返回NULL则这将会出现问题
    *ptr=1;  
    
	//释放空间
    free(ptr);
    ptr=NULL;
        
    return 0;
}

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

简单说就是你开辟了多大的空间就只能使用该空间,如果越过这块空间访问别的空间就是越界访问。

#include<stdio.h>
#include<stdlib.h>//malloc 和 free 都在<stdlib.h>的头文件里
int main()
{
    int *ptr ;
    ptr =(int*)malloc(40); //申请一个动态内存空间为40字节
    if(ptr==NULL) //防止申请空间失败传入了空指针
    {
        perror("ptr");
        return 1;
    }
 
    for(int i=0;i<20;i++)
    {
        *(ptr+i)=i;//申请的是四十个字节,这里产生了越界
    }
    
    free(ptr);
    ptr=NULL;
    return 0;
}
 

2.1. 越界访问的后果

  • 如果有不太清楚的老铁,下面是我在网上找到比较完好的资料,可以慢慢品味:

1.未定义行为:C语言标准规定,对于越界访问的行为,编译器不会提供任何保证,其结果是未定义的。这意味着程序可能会产生不可预测的结果,包括崩溃、错误输出、数据损坏等。

2.内存损坏:越界访问可能会破坏其他变量或数据结构的内存空间,导致程序出现异常行为或崩溃。

3.安全漏洞:一些恶意用户可能会利用越界访问漏洞来修改程序的执行流程,从而执行恶意代码或获取未授权的访问权限。

4.调试困难:越界访问可能会导致程序的行为变得不可预测,增加调试程序的难度。

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *p;
    *p=10;  
    free(p);  //这里的p并不是动态内存空间仍然进行了释放
    return 0;
}

记住一点:非动态无free;

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *p=(int*)malloc(sizeof(int)*2);
    if(p==NULL)
    {
        perror("malloc");
        return 1;
    }
    int i=0;
    for(i=0;i<5;i++)
    {    //这里只对前 5个进行赋值
		*p=i;
		p++;//这时p指针也会向高地址走去,
//这代表了p指针不再是指向这块空间的最初的起始位置地址
    }
    //而如果再对这块空间进行释放,还能只释放一部分么?
    //答案是不行的,程序会崩溃
    free(p); 
    p=NULL;
   
}

这种错误应该也是包含了另一个错误:内存泄漏;

如果释放的不是指向动态分配内存开始位置的指针,而是指向内存块中间位置的指针,那么会导致内存泄漏。因为从指针位置往后的内存空间仍然处于已分配状态,但是程序无法访问这些内存空间,从而造成内存泄漏。

所有在动态开辟空间时,最好不用动该空间的起始位置,最好重新创建一个指针进行操作,这也是一个好习惯哦;

5.对一块动态空间的多次释放

这种错误应该都是疏忽的原因,认为自己还没释放,结果的是已经释放了(真是哭死);

#include<stdio.h>
#include<stdlib.h>
int main()
{
	//开辟
    int *p=(int*)malloc(sizeof(int)*2);
    if(p==NULL)
    {
        perror("p");
    }
    
   //释放 
    free(p);
   //不记得自己的释放
   //~~~~~~
    free(p);  //再一次释放,会导致不可预测的后果;
    //   对这最好的解决办法就是:当我们释放了一块空间后,
    // 一定让他指为空指针
    p=NULL;
   
}
  • 因为如果你释放完一块空间后,就给予空指针进行维护,如果你还是忘记是不是释放了,再一次进行释放,那样有不会有太大的影响,因为我们提前的赋值空指针维护,而free空指针是什么都不会做的,所有没有太大影响。

当我们释放了一块空间后,一定让他指为空指针,这是一个好习惯哦;

6.忘记释放动态内存空间(内存泄漏)

前面提到了多次内存泄漏,终于到了

我们需要先看一段代码才好更好的理解:

void test()
{
    int*p = (int*)malloc(sizeof(int) * 2);
    //这是申请了这块空间返回地址给了指针 p
    if (p != NULL)
    {
        *p = 20;
    }
    //但是当退出该函数时,指针 p所占的空间会还给操作系统,
    //  这就代表刚刚开辟的空间无法再找到了,
    //     因为只有指针p指向该空间地址
}
int main()
{
    test(); 
    return 0;
}

如果还不理解可以这样帮助理解,我也是这样理解的:
假如你是一个卧底,只有你的一个单线上司 p 警官知道你的卧底身份,但是在完成一次任务中,p 警官死了,那现在就尴尬了,谁都不认识你了,这就相当于内存泄漏了;

切记动态开辟空间一定要记得释放,并且是正确的释放;这是一个好习惯哦。

多学一点点:

内存泄漏是一种慢性问题,它不会立即导致程序崩溃,但会逐渐占据系统内存,影响系统的稳定性和性能并最终导致系统崩溃。

内存泄漏(Memory Leak)是指程序在运行过程中无法释放已经分配的内存空间,导致系统中存在大量的无用的内存空间,最终可能导致程序崩溃甚至系统崩溃。


完结:

对于动态内存还是比较重要的,因为通常情况下:堆的空间是比栈的空间的是大的,同时我们知道,动态内存是可以进行修改的,我们想开辟多少字节的空间就开辟多少字节的空间,这一点就可以证明他的优点,所有在开辟动态内存空间时,一定要注意防止出现得不偿失!!!

——————————

最后小孩码文不易,感谢支持,有你的支持就是小孩最大的动力 ~~ ^ _ ^ ~~

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

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

相关文章

虹科案例 | 台积电为保证光罩运输质量选择MSR冲击振动记录仪!内含台积电工程师专访

晶圆运输需要注意什么&#xff1f; 晶圆运输是半导体制造过程中极为关键和敏感的一环。在晶圆运输过程中&#xff0c;需要注意以下几点&#xff1a; 1、静电防护 晶圆非常容易受到静电的干扰&#xff0c;因此在运输过程中需要遵守严格的静电防护措施。使用适当的静电防护包装…

数据库|同城双中心 DR Auto-Sync 主中心意外故障恢复

一、前言 最近&#xff0c;我一直在各个地方进行 TiDB 的 Poc 测试。在这些测试中&#xff0c;客户特别关注同城双中心或者两地三中心的架构体系&#xff0c;经常会找我了解 TiDB 灾备架构的实现方案和底层逻辑。基于客户对 RPO 0 的要求&#xff0c;我一般会向他们介绍 DR Au…

Flume原理剖析

一、介绍 Flume是一个高可用、高可靠&#xff0c;分布式的海量日志采集、聚合和传输的系统。Flume支持在日志系统中定制各类数据发送方&#xff0c;用于收集数据&#xff1b;同时&#xff0c;Flume提供对数据进行简单处理&#xff0c;并写到各种数据接受方&#xff08;可定制&…

软件安全测试包含哪些内容和方法?安全测试报告的必要性

软件安全测试是一种通过模拟真实攻击的方式&#xff0c;对软件系统进行全面的安全性评估和测试&#xff0c;以发现潜在的安全漏洞和弱点&#xff0c;是确保软件系统安全性的重要措施。在进行软件安全测试时&#xff0c;我们需要了解测试的内容和方法&#xff0c;以及为什么进行…

《高性能MySQL》——查询性能优化(笔记)

文章目录 六、查询性能优化6.1 查询为什么会慢6.2 慢查询基础&#xff1a;优化数据访问6.2.1 是否向数据库请求了不需要的数据查询不需要的记录多表关联时返回全部列总是取出全部列重复查询相同的数据 6.2.2 MySQL 是否在扫描额外的记录响应时间扫描的行数与返回的行数扫描的行…

项目经理和PMO如何穿越低谷,激活自己与团队——WOOP给你答案

2023年&#xff0c;已经还剩下不到5个月了。因为今年整体大环境不好&#xff0c;很多人会因为遇到各种问题&#xff0c;让自己掉入低谷&#xff0c;也有可能让自己带的团队毫无生气。我期待这篇文章能够给你带来向上的力量&#xff0c;在困境中看到希望与可能性。 相信有很多人…

如何在轻量级RTSP服务支持H.264扩展SEI发送接收自定义数据?

为什么开发轻量级RTSP服务&#xff1f; 开发轻量级RTSP服务的目的是为了解决在某些场景下用户或开发者需要单独部署RTSP或RTMP服务的问题。这种服务的优势主要有以下几点&#xff1a; 便利性&#xff1a;通过轻量级RTSP服务&#xff0c;用户无需配置单独的服务器&#xff0c;…

无涯教程-Perl - formline函数

描述 格式功能和相关的运算符使用此功能。它根据PICTURE的内容将LIST格式化为输出累加器变量$^ A。写入完成后,该值将写出到文件句柄中。 语法 以下是此函数的简单语法- formline PICTURE, LIST返回值 该函数总是返回1。 Perl 中的 formline函数 - 无涯教程网无涯教程网提…

中小企业在数字化转型上所面对的问题都有哪些?_光点科技

随着科技的飞速发展&#xff0c;数字化转型已经成为企业持续发展的必由之路。尤其是中小企业&#xff0c;数字化转型不仅可以提高效率&#xff0c;降低成本&#xff0c;还可以拓展市场&#xff0c;增强竞争力。然而&#xff0c;数字化转型并非一帆风顺&#xff0c;中小企业在这…

arcgis更改图层字段名脚本

话不多说&#xff0c;上脚本源码&#xff0c;复制黏贴即可 #-*- coding:utf-8 -*- __author__ lumen import arcpy #输入图层 InputFeature arcpy.GetParameterAsText(0) #原始字段 oldField arcpy.GetParameterAsText(1) # 获取原始字段类型 oldFieldType desc arcpy.…

电机基础知识::(1、电磁力;2力与运动)

永磁同步电机基础知识(一)_哔哩哔哩_bilibili

led台灯哪些牌子性价比高?推荐几款性价比高的护眼台灯

作为学龄期儿童的家长&#xff0c;最担心的就是孩子长时间学习影响视力健康。无论是上网课、写作业、玩桌游还是陪伴孩子读绘本&#xff0c;都需要一个足够明亮的照明环境&#xff0c;因此选购一款为孩子视力发展保驾护航的台灯非常重要。为大家推荐几款性价比高的护眼台灯。 …

influxDB

文章目录 版本2.0 数据结构Organization 组织Bucket 存储桶Measurementtagfieldtimestamp retention policy (RP) 保留策略Point 一条数据Series 一组数据 写入gzip压缩 查询FluxInfluxQL 官网 https://docs.influxdata.com/v1.8 中文翻译文档 https://influxdb-v1-docs-cn.cno…

iPhone手机怎么恢复出厂设置(详解)

如果您的iPhone遇到了手机卡顿、软件崩溃、内存不足或者忘记手机解锁密码等问题&#xff0c;恢复出厂设置似乎是万能的解决方法。 什么是恢复出厂设置&#xff1f;简单来说&#xff0c;就是让手机重新变成一张白纸&#xff0c;将手机所有数据都进行格式化&#xff0c;只保留原…

TIA博途选型工具Selection Tool的基本使用方法介绍

TIA博途选型工具Selection Tool的基本使用方法介绍 如下图所示,下载选型工具后,解压缩,可以看到SelectionTool.exe执行程序, 感兴趣的可从以下链接获取该软件: TIA博途选型工具TIA Selection Tool-2023最新 无需安装,直接双击打开该软件, 如下图所示,进入软件后(默认…

用栈判断是否匹配

1 问题 写代码的时候用到的括号都是成双成对的出现&#xff0c;并且大小也相同。在集成编辑环境中&#xff0c;IDE就会为我们自己动检查括号是否匹配。那么为了避免在报错&#xff0c;如何判断是否有无括号不匹配&#xff1f; 2 方法 利用栈来实现这种功能。当遇见一个左括号&a…

为什么Mendix的OQL比SQL更方便,以及如何实现类似MySQL的workbench?

前言 在当今信息时代&#xff0c;数据的价值变得越来越重要。数据处理是任何软件系统都非常关注的核心功能。无论是电子商务网站、移动应用程序还是企业管理系统&#xff0c;这些系统都需要处理和管理大量的数据。例如&#xff0c;当用户在电子商务网站上搜索特定商品时&#…

【技术】Web实时消息推送方式整理

【技术】Web实时消息推送方式整理 SSE 单向 服务器 --> 客户端 text/event-stream类型的数据流信息 实现 客户端 <script>let source null;let userId 7777if (window.EventSource) {// 建立连接source new EventSource(http://localhost:7777/sse/sub/userId);…

DC电源模块关于多路输出的问题

BOSHIDA DC电源模块关于多路输出的问题 DC电源模块通常具备多路输出功能&#xff0c;这使得它在实际应用中具有极高的灵活性和可扩展性。当需要为多个不同的负载提供电源时&#xff0c;多路输出的设计可以降低整个系统的成本和复杂度&#xff0c;同时也可以减少系统空间的占用。…

docker菜谱大全

记录docker常用软件安装&#xff0c;欢迎大家投稿。&#x1f60e;&#x1f60e;&#x1f60e; 相关文档&#xff1a; DockerHub&#xff1a;https://hub.docker.com/Linux手册&#xff1a;https://linuxcool.com/Docker文档&#xff1a;https://docs.docker.com/Docker中文网&a…