动态内存管理(malloc,calloc,realloc,free)+经典笔试题

news2024/10/5 23:31:12

动态内存管理

  • 一. malloc 和 free
    • 1. malloc
    • 2. free
  • 二. calloc
  • 三. realloc
  • 四.动态内存的错误
    • 1.对NULL指针的解引用操作
    • 2.对动态开辟空间的越界访问
    • 3.对非动态开辟内存使用free释放
    • 4.使用free释放一块动态开辟内存的一部分
    • 5.对同一块动态内存多次释放
    • 6.动态开辟内存忘记释放(内存泄漏)
  • 五.动态内存经典笔试题分析

前言:

  1. 当我们要开辟一块连续的内存空间时,我们第一时间想到的可能是数组。但是一但开辟了数组,数组的大小就确定了,无法调整数组的大小。
  2. 有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
  3. 于是动态内存开辟函数(malloc,calloc,realloc,free)应运而生,下文带您一一了解其中的奥秘。

一. malloc 和 free

1. malloc

void* malloc(size_t size);

解释:在堆区中开辟一块大小为 size 个字节的空间,返回指向这块空间的起始地址(泛型指针void*)

因为这块空间存放的数据类型不知(由程序员自己确定),所以用泛型指针接收该地址,在使用的时候记得养成一个好习惯:强制类型转换为自己需要的数据类型。

  1. 如果开辟成功,则返回一个指向开辟好空间的指针。

  2. 如果开辟失败,则返回一个 NULL 指针,因此malloc的返回值一定要做检查。

  3. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候程序员自己来决定。

  4. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

2. free

void free(void* ptr);

解释:free是用来对动态内存的释放和回收的。free 对指针 ptr 指向的内容释放掉,但是指针仍然指向这块空间,若后面不再使用,及时将 ptr 置为 NULL,否则产生野指针。

  1. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

  2. 如果参数 ptr 是NULL指针,则函数什么事都不做。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	//在堆区申请10个整形空间
	int* p=(int*)malloc(10*sizeof(int));
	if (p == NULL)
	{
		//开辟空间失败
		perror("malloc");//打印错误信息
		//printf("%s\n", strerror(errno));//也是打印错误信息
		return 1;
	}
	//使用这块空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//打印这块空间
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放这块空间
	free(p);//将这块空间还给了操作系统,我们已经没有权限再使用这块空间了
	        //但是p仍然指向那块空间

	p = NULL;//若不将p置为NULL,那么p就是野指针
	return 0;
}

在这里插入图片描述

总结:

  1. 动态内存开辟的函数头文件都是 stdlib.h。
  2. 如果不释放的话,程序结束的时候也会被操作系统自动释放。
  3. 但是为了防止内存泄漏,将其置为NULL。这是一个好习惯。

二. calloc

void* calloc(size_t num, size_t size);

解释:在堆区中开辟一块大小为 num * size 个字节的空间,返回指向这块空间的起始地址,其中 num 为数据的个数,size 为单个数据的字节数,同时把申请的空间的每个字节初始化为全为0。


#include<stdio.h>
#include<stdlib.h>
int main()
{
	//在堆区申请10个整形空间
	int* p = (int*)calloc(10, 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;
}

在这里插入图片描述

三. realloc

void* realloc (void* ptr, size_t size);

解释:调整动态内存开辟的空间,ptr 是那块空间的起始地址,size 是调整后的那块空间的字节的个数,返回指向这块空间的起始地址。

#include<stdio.h>
#include<stdlib.h>
int main()
{
	//在堆区申请10个整形空间
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//调整空间——变成20个整形空间
	int* ptr = (int*)realloc(p, 20 * sizeof(int));//注意:要用新的指针来接收
	if (ptr != NULL)
	{
		p = ptr;
	}
	else
	{
		//开辟失败
		return 1;
	}
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		*(p + i) = i + 1;
	}
	for (i = 0; i < 20; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

在这里插入图片描述

注意:也许有些人有疑问为什么要用新的指针接收返回的地址,直接用原来的指针接收不行吗?答案是不行的,在realloc调整动态内存开辟的空间有3中情况,代码如下:

int main()
{
	int* p = (int*)malloc(10);
	//...
	if (p != NULL)
	{
		int* ptr = (int*)realloc(p, 20);
		//...
	}
	return 0;
}

情况1:

  1. 开辟的空间后面有足够且连续的空间,只需返回空间的起始地址即可。
    在这里插入图片描述

情况2:

  1. 如果后续的空间不够,realloc 函数直接在堆区找一块新的满足大小的空间,将旧的地址,拷贝到新的地址。
  2. 自动释放旧的地址指向的空间,不需要手动 free,返回新的空间的起始地址。

在这里插入图片描述

情况3:

  1. 堆区已经没有满足情况的连续空间了,返回NULL。
    在这里插入图片描述

realloc函数也能开辟空间,代码如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)realloc(NULL, 10 * sizeof(int));//等价于malloc(40)
	if (p == NULL)
	{
		//...
	}
	return 0;
}

四.动态内存的错误

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

  1. 如果将一个空指针(NULL)进行解引用操作,程序会遇到未定义行为,会导致程序崩溃。这是因为空指针并不指向任何有效的内存地址,尝试解引用它会导致访问非法内存,从而导致程序崩溃。
  2. 因此,在解引用指针之前,应该始终先检查指针是否为空。

错误代码如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));//可能会开辟失败导致,p等于NULL
	//if (p == NULL)
	//{
	//	  perror("malloc");
	//	  return 1;
	//}
	
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;//如果p等于NULL,对其进行解引用操作,程序会崩溃
	}

	free(p);
	p = NULL;
	return 0;
}

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 40; i++)//越界访问,程序崩溃
	{
		*(p + i) = i + 1;
	}

	free(p);
	p = NULL;
	return 0;
}

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

#include<stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	//...

	free(p);//程序崩溃
	p = NULL;
	return 0;
}

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;//修改了指针p
	}

	free(p);//free释放一块动态开辟内存的一部分,程序崩溃
	p = NULL;
	return 0;
}

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	free(p);//p为野指针

	//...
	free(p);//对同一块动态内存多次释放,程序崩溃
	p = NULL;
	return 0;
}

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

  • 内存泄漏:在程序执行过程中,动态分配的内存空间在程序不再需要时没有被正确释放的情况。这会导致程序在运行过程中持续耗费内存空间而不释放,最终可能导致系统性能下降,甚至导致程序崩溃。
#include<stdio.h>
#include<stdlib.h>
void test()
{
	int flag = 1;
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
		return;
	//使用
	if (flag)
		return;//未释放,函数提前结束,就找不到那块空间,导致内存泄漏
		
	free(p);
	p = NULL;
}
int main()
{
	test();
	//......
	//只有程序结束了,空间才被释放
	return 0;
}
  1. 在一些服务器上(腾讯,阿里…),可能7*7=49天一直在运行,若一直申请内存而不释放,内存迟早有一天会耗尽的,这会造成巨大损失。
  2. 动态内存管理是一把双刃剑:提供灵活的内存管理方式,但是会带来风险。
  3. 切记:动态开辟的空间一定要释放,并且正确释放。

五.动态内存经典笔试题分析

例题1:

在这里插入图片描述
解决办法:

  1. 传递 str 的地址通过地址修改 str ,同时可以释放动态内存开辟的空间。
  2. 返回动态内存开辟的空间的地址,可以释放动态内存开辟的空间。
    在这里插入图片描述

例题2:

在这里插入图片描述

例题3:

在这里插入图片描述

解决办法:
在这里插入图片描述

创作不易,如果能帮到你的话能赏个三连吗?感谢啦!!!

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

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

相关文章

LabVIEW液压伺服压力机控制系统与控制频率选择

液压伺服压力机的控制频率是一个重要的参数&#xff0c;它直接影响系统的响应速度、稳定性和控制精度。具体选择的控制频率取决于多种因素&#xff0c;包括系统的动态特性、控制目标、硬件性能以及应用场景。以下是一些常见的指导原则和考量因素&#xff1a; 常见的控制频率范…

Docker容器技术原理

深入剖析Linux容器 Docker本质是宿主机上的进程&#xff0c;通过namespace实现资源隔离&#xff0c;通过cgroups实现资源限制 docker深度学习-github Docker技术原理 Docker 是利用 Linux 的 Namespace 、Cgroups 和联合文件系统三大机制来保证实现的&#xff0c; 所以它的原…

AI和机器人引领新一轮农业革命

AI和机器人技术在农业领域的应用正在迅速发展&#xff0c;未来它们可能会实现厘米级精度的自主耕作。 精确种植&#xff1a;AI算法可以分析土壤条件、气候数据和作物生长周期&#xff0c;以决定最佳种植地点和时间。 土壤管理&#xff1a;利用传感器和机器学习&#xff0c;机器…

TEE的密码学算法都支持哪些?你们都是如何实现的?

先说TEE OS中都支持哪些密码学算法?需要支持哪些密码学算法? 我们从GP规范的角度来看。在一般的tee中,都号称支持GP规范。GP规范里要求的强制支持的算法,一般都会实现。对于GP规范里可选的算法,并不是每一家TEE都能支持的。 强制要求的算法 可选支持的算法有 再来看TEE O…

python pip 安装

如果您不确定pip的安装路径&#xff0c;可以通过以下命令来查询&#xff1a; pip show pip 这个命令会显示pip的详细信息&#xff0c;其中包括pip安装的路径。如果您想修改pip的默认安装路径&#xff0c;可以使用pip的"--target"参数指定目标路径&#xff0c;例如&a…

堡垒机的自动化运维,快速安全提升运维效率

随着信息技术的突飞猛进&#xff0c;企业对于IT系统的依赖程度日益加深&#xff0c;不仅希望可以提高运维效率&#xff0c;也希望能保障IT系统的安全。因此堡垒机与自动化运维技术的结合应运而生&#xff0c;堡垒机的自动化运维&#xff0c;快速安全提升运维效率。今天我们就来…

Android 绑定服务的5个问题。

1.android studio 目录结构改变了。为什么会报R 资源文件找不到。 在写项目的时候经常需要改到。 gradle文件里的域名名字要改变下。 2.Caused by: android.app.BackgroundServiceStartNotAllowedException: Not allowed to start service Intent { cmpcom.zjtzsw.sbkDevice/…

Arthas使用教程——JVM常用命令

JVM相关命令 dashboard——当前系统的实时数据面板 显示当前 tomcat 的实时信息。 使用方式&#xff1a;dashboard 数据说明 ID: Java 级别的线程 ID&#xff0c;注意这个 ID 不能跟 jstack 中的 nativeID 一一对应。 NAME: 线程名 GROUP: 线程组名 PRIORITY: 线程优先级…

探索多头混合专家(MH-MoE)模型的先进架构

在深度学习领域&#xff0c;大型模型如大型语言模型&#xff08;LLMs&#xff09;和大型多模态模型&#xff08;LMMs&#xff09;因其在各个领域的有效性而受到广泛关注。然而&#xff0c;这些模型在训练和推理时面临着巨大的计算成本。为了解决这一问题&#xff0c;研究者们提…

【Python】深入认识Python数据类型和变量

​​​​ 文章目录 1. 引言数据类型的重要性Python中的数据类型概述 2. 数字类型整型&#xff08;int&#xff09;浮点型&#xff08;float&#xff09;复数&#xff08;complex&#xff09; 3. 字符串类型字符串的定义与使用字符串操作方法 4. 布尔类型布尔值和布尔运算 5. 列…

【RK3568】制作Android11开机动画

Android 开机 logo 分为两种&#xff1a;静态显示和动态显示。静态显示就是循环显示一张图片&#xff1b;动态显示就是以特定帧率顺序显示多张图片 1.准备 android logo 图片 Android logo最好是png格式的&#xff0c;因为同一张图片的情况下&#xff0c;png 格式的比 jpg和b…

kafka命令--简单粗暴有效

zookeeper bin目录下执行 启动&#xff1a;./zkServer.sh start 停止&#xff1a;./zkServer.sh stop 重启&#xff1a;./zkServer.sh restart 状态&#xff1a;./zkServer.sh status kafka bin目录下执行 启动&#xff1a;./kafka-server-start.sh -daemon …/config/server.…

三款强大的Node.js版本控制工具推荐

在Web开发领域&#xff0c;Node.js无疑是一个热门的JavaScript运行时环境&#xff0c;它允许开发者使用JavaScript来构建服务器端应用程序。随着Node.js版本的不断更新&#xff0c;管理多个Node.js版本变得尤为重要。以下是三款实用的Node.js版本管理工具&#xff0c;它们可以帮…

VS2015安装模块时报错:安装包丢失或损坏,,,,,无效的驱动器

解决&#xff1a; 1、解压.iso文件&#xff1b; 2、替换目录 替换为解压后的对应的目录&#xff0c;点击重试即可。

二说springboot3的自动配置机制

大家好&#xff0c;这里是教授.F 目录 SpringBootApplication&#xff1a; EableAutoConfiguration&#xff1a; 上一篇文章粗略的讲了自动配置机制&#xff0c;二说系列将从源码的角度进行讲解。 SpringBootApplication&#xff1a; 首先我们还是得从SpringBootApplication…

大数据之HDFS磁盘扩容(linux磁盘扩容)

之所以扩容,是因为当前大数据平台已经接入了不同来源的数据,当执行mapreduce任务时,会发生磁盘爆满,导致hdfs爆红 具体扩容方案如下: 1、查看云磁盘分区情况 fdisk -l . 可以从图看出&#xff1a; /dev/vda 数据盘磁盘容量为21.5GB&#xff0c;包含/dev/vda1分区 /dev/vdb 数…

GD32F4XX的ISP方式下载程序时的串口选择

官方资料 详细信息可参考GD32F4xx的用户手册&#xff0c;第 1.4 章节 引导配置 。 版本是 &#xff1a;GD32F4xx_User_Manual_Rev3.0_CN 资料链接: https://www.gd32mcu.com/cn/download/6?kwGD32F4

Vue3【八】ref创建基本类型的响应式数据

ref创建基本类型的响应式数据 在setup中直接声明变量不是实时变化的响应式数据 要想使基本类型数据变成响应式数据&#xff0c;需要用到ref()函数 案例截图 案例目录 案例代码 Person <template><div class"person"><h1>我是Person组件</h1&g…

数据结构~~排序

目录 一、排序的概念 二、插入排序 直接插入排序 希尔排序 三、选择排序 选择排序 堆排序 四、交换排序 冒泡排序 快速排序 递归实现 非递归实现 五、归并排序 递归 非递归 六、非比较排序&#xff08;计数排序&#xff09; 七、其他排序 基数排序 桶排序 八…

使用JMeter软件压测接口配置说明

1、下载完该软件https://blog.csdn.net/wust_lh/article/details/86095924 2.点击bin文件中jmeter.bat脚本https://blog.csdn.net/wust_lh/article/details/86095924 3.官网地址https://jmeter.apache.org/download_jmeter.cgi 通过 【Options】->【Choose Language】变更为…