动态内存分配及管理——C语言

news2024/11/18 1:29:04

目录

一、为什么存在动态内存分配

二、动态内存函数介绍

2.1 malloc

2.2 free

2.3 calloc

2.4 realloc

三、常见的动态内存错误

3.1 对NULL指针的解引用操作

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

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

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

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

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


        今天,博主给大家带来的是动态内存分配的学习和讲解。在之前,我们学习了通讯录,文章中利用到一些动态内存分配的一些知识,有些可能大家会看不懂,那么相信通过今天的这篇文章,大家的问题就会迎刃而解。本篇,我们将从“为什么存在内存分配”,“动态内存函数介绍”,以及“常见的动态内存错误”三个板块来为大家一 一解答。

一、为什么存在动态内存分配

在之前,我们学过的内存开辟有哪些呢?比如,创建一个变量,或者创建一个数组。

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

上面两种开辟方式是我们常用的开辟内存的方式,但是这两种开辟内存空间的方式有两个特点:

① 空间开辟大小是固定的

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

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

二、动态内存函数介绍

在学习动态内存函数之前,我们需要知道动态内存开辟的空间是放在堆区的,如上图所示,局部变量和形式参数占用的空间是在栈区的,全局变量以及静态变量开辟的空间是在静态区的。 

2.1 malloc

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

void* malloc  (   size_t    size   ) ;
这个函数向内存申请一块 连续可用 的空间,并返回指向这块空间的指针。
① 如果开辟成功,则返回一个指向开辟好空间的指针。
② 如果开辟失败,则返回一个NULL指针,因此 malloc 的返回值一定要做检查。
③ 返回值的类型是 void* ,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
④ 如果参数 size 为 0 malloc 的行为是标准是未定义的,取决于编译器。
举个代码例子来解释吧!
int main()
{
	int* p = (int*)malloc(40);//开辟40个字节的空间
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//开辟成功
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));
	}

	return 0;
}
因为返回的类型是void*,所以我们要根据自己的需求来进行强制类型转换,其次,我们需要判断是否开辟成功,如果返回值为NULL指针,那么就结束了,反之则是开辟成功。然后我们打印一下看看开辟成功的空间里面是什么。

此时我们发现开辟的空间里面存的是一堆没见过的随机数数,其实是malloc函数申请的空间,在申请成功后,直接返回这片空间的起始地址,不会初始化空间的内容。 

2.2 free

C 语言提供了另外一个函数 free ,专门是用来做动态内存的释放和回收的,函数原型如下:
void free void*   ptr  ) ;
上面我们学习了malloc函数,我们发现,malloc只负责申请空间,那么申请的这个空间当我们使用完之后会怎么样呢?其实这块空间并不会主动的还给操作系统,除非程序结束,否则这块空间将会一直存在堆区。这个时候就需要另一个内存函数了。
总的来说: malloc申请的内存空间,当程序退出时,还给操作系统,当程序不退出时,动态申请的内存不会主动释放。需要free函数来释放空间。
具体怎么使用呢,直接把我们动态开辟的空间的起始地址传入free函数中即可,代码如下图所示:
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//开辟成功
	free(p); //释放开辟的空间
	p = NULL;  //置空
	return 0;
}

注意:p本来指向的空间被释放后,p就变成野指针了,比较危险,这时候我们需要主动将p置为空指针。

free只能释放动态开辟的空间,不能释放静态区或者栈区开辟的空间(标准未定义) 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

② 如果参数 ptr NULL 指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头文件中

2.3 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));//开辟十个连续的sizeof(int)大小的空间
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//打印数据
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

当我们打印完之后,发现calloc会把每个字节初始化为0。

总的来说:calloc函数和malloc函数很相似,功能也是一样,唯一不同的就是,会把开辟的每个字节都初始化为0。

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

2.4 realloc

realloc 函数的出现让动态内存管理更加灵活。
        有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
函数原型如下:
void*   realloc  void*   ptr  ,   size_t   size  ) ;
① ptr 是要调整的内存地址(也就是被调整空间的起始地址,这块空间之前已经开辟好了)
如果ptr为空指针,那么它的功能和malloc就是一样的,开辟一个新的空间。
② size 调整之后新大小 (需要调整的新的空间的大小)
③ 返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 的空间。
④ realloc 在调整内存空间的是存在两种情况:
情况1 :原有空间之后有足够大的空间
情况2: 原有空间之后没有足够大的空间
情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用,同时把原来空间的内容拷贝过来,然后自动释放原来的内存空间,这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些,我们这里用代码来举例子吧:
 
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//初始化为1~10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i + 1;
	}
	//增加一些空间
	int* ptr = (int*)realloc(p, 80);
	if (ptr != NULL)  //开辟成功
	{
		p = ptr;
		ptr = NULL;
	}
	else  //开辟失败
	{
		perror("realloc");
		return 1;
	}
	//开辟成功,打印数据
	for (i = 0; i < 20; i++)
	{
		printf("%d ", p[i]);
	}
	free(p);  //释放空间
	p = NULL;  //置空
	return 0;
}

注意:考虑到可能开辟失败,所以我们需要先进行判断一下,如果开辟成功,则将返回的指针赋值给p来维护。

当我们开辟完之后,打印一下看看效果。

当我们使用完动态开辟的内存之后,仍然需要手动去释放内存空间。

 当然,上面情况只是减少增加空间,如果要减少空间就比较简单了,直接在原来的基础上减少,地址返回的也是原来的地址。

好了,到这里动态内存管理的基本知识就介绍清楚了,实际上把这四个内存函数了解清楚,基本上对动态内存的知识点也就基本掌握了。接下来,我们来了解一下动态内存管理常见的内存错误,通过解释这些错误,来更清楚更深入的了解动态内存管理。

三、常见的动态内存错误

3.1 对NULL指针的解引用操作

当我们动态内存开辟的时候,会存在开辟失败的情况,此时返回的就是空指针。

如下代码就是一个典型的例子:

void test()
{
    int *p = (int *)malloc(INT_MAX/4);
    *p = 20;//如果p的值是NULL,就会有问题
    free(p);
}

此时动态内存开辟可能失败了,就导致返回的指针为空指针,也就是p为空指针,如果再对p这个空指针进行解引用操作,那么就会报错 。为了解决这种问题,我们在平时写代码的时候,为了避免空指针异常,要对动态开辟的空间返回的指针进行空指针判断检查。(好习惯)

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

这里直接拿代码来解释吧!

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);
}

这段代码中,我们使用malloc开辟40个字节大小的空间,然后进行空指针检查判断,紧接着,在我们赋值的时候,我们最多只能访问到p[9]这块空间,代码中我们i的最大值为10,此时很明显,就出现了越界访问。

总的来说:开辟多少空间,就只能使用多少空间,没有开辟的,属于操作系统的空间,我们不可以随便进行操作访问。

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

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

在前面讲free的时候说过,free释放的空间,只能是动态内存开辟的空间,不能是静态区或者栈区开辟的空间,上面代码的例子中,a是局部变量,不是动态开辟的空间,所以不能被free释放。

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

int main()
{

	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i + 1;
		p++;  //此时p不再指向malloc开辟的空间的起始地址
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

上面代码中,我们使用malloc开辟一块空间,然后将起始地址返回给p,也就是说p指向molloc动态开辟的空间的起始地址,紧接着进行空指针检查判断,然后给p指向的空间进行赋值,但是,在赋值的过程中,p的指向发生了变化(如下图),不再指向malloc开辟的空间的起始地址,此时在对p指向的空间进行释放,这种做法是不被允许的,会报错。

总结:free中的参数只能是动态内存开辟空间的起始地址,对于动态开辟的空间的起始地址不要随便的更改指向。

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

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

上面这种情况也是一直很低级的错误,也是不被允许的,会报错,最好的解决办法就是,在释放完之后,将p指针置为空,这样在后面多次释放也不影响,因为p已经是空指针了。

总结:不能对同一块动态内存多次释放,解决办法:在第一次释放完之后,将指针置为空指针(好习惯)

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

void test()
{
    int *p = (int *)malloc(100);
    if(NULL != p)
    {
        *p = 20;
    }
}
int main()
{
    test();
    while(1);
}

对于上面这个代码,在test这个函数种,我们开辟了100个字节的动态空间,但是在最后,我们并没有对这块空间进行free释放,这时候当我们跳出函数之后,指针变量p也销毁了,这个时候,p原来指向的空间我们根本找不到了,但是这块空间仍然存在,仍然被占用,只是我们丢失了它的起始地址,不能再对这块空间进行操作或者访问了,这就造成了这块空间仍然存在占用内存,但是我们却访问不到,并无法释放,这就是所谓的内存泄露。

针对这个问题的解决:在我们使用完动态空间之后,一定要进行free释放。

总结:动态开辟的空间一定要释放,并且正确释放(切记)

好了,今天的动态内存分配和管理讲到这里就结束了,听到这里,相信大家的一些关于动态内存分配管理的问题就会迎刃而解了吧。如果哪里有问题,欢迎在评论区留言。如果觉得小编写的还不错的,那么可以一键三连哦,您的关注点赞和收藏是对小编最大的鼓励。谢谢大家!!!

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

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

相关文章

AUTOSAR NvM协议栈集成方法

一、涉及的模块 Bsw&#xff1a;NvM、MemIf、Fee、Crc Mcal&#xff1a;Fls 其中一些芯片厂商的MCAL也会提供Fee模块&#xff0c;本文选择使用ETAS提供的Fee模块&#xff0c;好处是Fee的Block不需要手动配&#xff0c;在NvM中配好了Block之后&#xff0c;生成Bsw代码的同时会…

c语言——查找特定字符在字符串中出现的次数

fgets 函数用于从标准输入&#xff08;stdin&#xff09;中读取一行字符串&#xff0c; 并将其存储在指定的字符数组 str 中。 sizeof str/sizeof str[0] 是用来计算字符数组 str 的大小。 这个表达式计算的结果是字符数组 str 可以容纳的元素个数&#xff08;包括…

linux vscode 下开发

linux vscode 下开发 javajdk插件 java jdk 各种JAVA JDK的镜像分发 编程宝库 - 技术改变世界 jdk 镜像 ubuntu22.04 安装 # Linux x64 64位 jdk-8u351-linux-x64.tar.gztar -zxf jdk-8u351-linux-x64.tar.gz mv jdk1.8.0_351 jdk8/ vim ~/.profile expo…

KEY接口ESD静电保护器件:DW05D5BC-B-E和DW05MS-S

静电在我们日常生活中&#xff0c;可谓是无处不在&#xff0c;我们身上和周围环境就带有很高的静电电压。都知道&#xff0c;ESD静电放电会对电子设备带来威胁甚至损坏。为此&#xff0c;电子设备中按键部位、接口部位以及触摸屏等防静电保护措施是非常有必要的。针对按键KEY接…

Java网络编程(一)网络基础

概述 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统、网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递 网络分类 局域网(LAN) 局域网是一种在小区域内使用的,由多台计算机组成的网络,覆盖范围…

如何使用Python获取SAP Temporary key 临时密钥

什么是Sap 临时密钥&#xff1f; SAP 临时密钥是用于激活 SAP 软件的一种许可证&#xff0c;只能在有限的时间内使用。这些密钥可用于评估目的或在系统迁移期间使用。它们是临时解决方案&#xff0c;通常在一定时间后过期。 如何获取Sap Temporary keys? 临时密钥: 如果创建…

Web网页浏览器远程访问jupyter notebook服务器【内网穿透】

文章目录 前言1. Python环境安装2. Jupyter 安装3. 启动Jupyter Notebook4. 远程访问4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5. 固定公网地址 前言 Jupyter Notebook&#xff0c;它是一个交互式的数据科学和计算环境&#xff0c;支持多种编程语言&#xff0c;如…

【仿写tomcat】五、响应静态资源(访问html页面)、路由支持以及多线程改进

访问html页面 如果我们想访问html页面其实就是将本地的html文件以流的方式响应给前端即可&#xff0c;下面我们对HttpResponseServlet这个类做一些改造 package com.tomcatServer.domain;import com.tomcatServer.utils.ScanUtil;import java.io.IOException; import java.io…

全国产EtherCAT运动控制边缘控制器(二):统一的上位机API接口

上节课程我们介绍了全国产EtherCAT运动控制边缘控制器ZMC432H的硬件接口与功能&#xff0c;本节课程我们主要讲解一下正运动API函数封装原理以及自定义API封装例程。 一、功能简介 全国产EtherCAT运动控制边缘控制器ZMC432H是正运动的一款软硬件全国产自主可控&#xff0c;运…

Genoss GPT简介:使用 Genoss 模型网关实现多个LLM模型的快速切换与集成

一、前言 生成式人工智能领域的发展继续加速&#xff0c;大型语言模型 (LLM) 的用途范围不断扩大。这些用途跨越不同的领域&#xff0c;包括个人助理、文档检索以及图像和文本生成。ChatGPT 等突破性应用程序为公司进入该领域并开始使用这项技术进行构建铺平了道路。 大公司正…

SOLIDWORKS有限元分析

SOLIDWORKS是一款广泛使用的三维计算机辅助设计软件&#xff0c;同时它还具有强大的有限元分析功能。有限元分析是一种工程分析方法&#xff0c;它将复杂的实体分解成许多小的有限元素&#xff0c;以便对其进行数学建模和分析。SOLIDWORKS的有限元分析功能可以帮助工程师预测和…

nbcio-boot3.1 解决积木报表基于SSTI的任意代码执行漏洞,积木报表版本从1.4.0升级到最新的1.6.1

1、积木报表基于SSTI的任意代码执行漏洞 使用JeecgBoot 受影响版本中由于积木报表 /jeecg-boot/jmreport/queryFieldBySql Api接口未进行身份校验&#xff0c;使用 Freemarker 处理用户用户传入的 sql 参数&#xff0c;未经授权的攻击者可发送包含恶意 sql 参数的 http 请求&am…

vue3动态组件

1 、 可以通过 shallowRef 把 可以把组件进行包裹 <template><div><el-button click"setclick(son1)">1111</el-button><el-button click"setclick(son2)">2222</el-button><el-button click"setclick(son…

数字变有趣,分析师的必备利器——奥威BI数据可视化工具

数据分析不仅要在短时间内把数据情况摸清、把数据关联梳理清楚&#xff0c;还需要以一直直观易懂的方式将数据信息准确传递给使用者&#xff0c;便于使用者利用这些数据信息去决策管理&#xff0c;这就是数据可视化。说到这&#xff0c;就不得不请出让数据变得有趣、易懂&#…

解决Java中的“Unchecked cast: java.lang.Object to java.util.List”问题

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

使用Scrapy构建自己的数据集

一、说明 当我第一次开始在工业界工作时&#xff0c;我很快意识到的一件事是&#xff0c;有时你必须收集、组织和清理自己的数据。在本教程中&#xff0c;我们将从一个名为FundRazr的众筹网站收集数据。像许多网站一样&#xff0c;该网站有自己的结构、形式&#xff0c;并有大量…

vs code 环境变量的配置

问题 环境变量中重复出现下面这两项 ..:/home/xxx/.local/bin/:/home/xxx/.local/bin/:...这造成了一些环境污染&#xff0c;因为/home/xxx/.local/bin 这个环境变量放在前面&#xff0c;文件夹里面的可执行的文件会比conda环境更加优先地执行。 解决 先说结论&#xff0c;…

数字孪生重塑生产格局:智慧工厂的前景与挑战

随着科技的飞速发展&#xff0c;数字孪生技术在智慧工厂的建设中正展现出令人瞩目的作用。数字孪生&#xff0c;一种将实际物理对象与数字虚拟模型相结合的前沿技术&#xff0c;不仅改变了生产方式&#xff0c;更为智慧工厂的未来描绘了一幅令人振奋的画卷。 在智慧工厂的建设…

Java实现敏感词过滤功能

敏感词过滤功能实现 1.GitHub上下载敏感词文件 2.将敏感词文件放在resources目录下 在业务中可以将文本中的敏感词写入数据库便于管理。 3.提供实现类demo 代码编写思路如下&#xff1a;1.将敏感词加载到list中&#xff0c;2.添加到StringSearch中&#xff0c;3.校验&#x…

SSL证书如何使用?SSL保障通信安全

由于SSL技术已建立到所有主要的浏览器和WEB服务器程序中&#xff0c;因此&#xff0c;仅需安装数字证书或服务器证书就可以激活功能了。SSL证书主要是服务于HTTPS&#xff0c;部署证书后&#xff0c;网站链接就由HTTP开头变为HTTPS。 SSL安全证书主要用于发送安全电子邮件、访…