C语言中动态内存管理

news2025/1/1 22:56:19

前言:为什么存在动态内存分配,为什么要用动态内存分配,动态内存分配的意义。鸡汤:有了坚持不一定成功,但没有坚持,就注定失败,各位也要努力坚持提升自己!

在这里插入图片描述

动态内存分配

    • 动态内存函数:malloc与free
      • malloc函数
      • free函数
    • calloc函数
    • realloc函数
    • 常见的动态内存错误
      • 对NULL指针的解引用操作
      • 对动态开辟空间的越界访问
      • 对非动态开辟内存使用free释放
      • 使用free释放一块动态开辟内存的一部分
      • 对同一块动态内存多次释放
      • 动态开辟内存忘记释放(内存泄漏)
    • 几个经典的笔试题

为什么存在动态内存分配?
我们已经知道的内存开辟方式有:

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

但是上述内存开辟的方式有俩个特点:

  • 空间开辟的大小是固定的
  • 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配

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

动态内存函数:malloc与free

malloc函数

C语言提供了一个动态内存开辟的函数:
我们可以通过之前给定查找库函数的网站去看看malloc的作用

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

不仅如此C语言还专门提供了一个库函数free专门用来做动态内存释放和回收的。

free函数

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

malloc和free都声明在 stdlib.h 头文件中。
当然,如果我们只看文字的话肯定是不能透彻的理解这些库函数的,上例题来讲解。
例题:

int main()
{
	//代码1
	int num = 0;
	scanf("%d", &num);
	int arr[num] = { 0 };//显然这种方式是无法实现动态内存的
	//在c语言中是没有变长数组的概念,在数组中的必须是常量
	//代码2
	int* ptr = NULL;
	ptr = (int*)malloc(num * sizeof(int));//通过使用malloc开辟num个整型的空间
	if (NULL != ptr)//判断ptr指针是否为空
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			*(ptr + i) = 0;//将数组中的所有值全部赋值为0
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//如果不及时释放所开辟的空间,并指向空指针容易造成野指针的状况
	return 0;
}

calloc函数

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。

① 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
②与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。这里是引用
当然我们依然要在实战中来运用。

例题:

int main()
{
	int* p = (int*)calloc(10, sizeof(int));//开辟10个整型的空间
	if (NULL != p)
	{
		for (int i = 0; i < 10; i++)
		{
			printf("%d\n", *p);//打印开辟空间的值
		}
	}
	free(p);//释放开辟的空间
	p = NULL;//
	return 0;
}

我们来看看运行结果是不是自动把开辟的空间初始化成了0
运行结果:
在这里插入图片描述
与此同时,我们在看看用malloc开辟看看会不会也有初始化

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL != p)
	{
		for (int i = 0; i < 10; i++)
		{
			printf("%d\n", *p);
		}
	}
	free(p);
	p = NULL;
	return 0;
}
 

运行结果:
在这里插入图片描述
此时我们发现,使用malloc开辟空间并没有对开辟空间的值进行初始化。此时很多读者想问那他们在开辟空间的时候有什么区别吗?区别就在于使用者在使用的时候有什么需求,就去用什么。

realloc函数

realloc函数的出现让动态内存管理更加灵活。
①有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的分配内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
②ptr 是要调整的内存地址
③size 调整之后新大小
④返回值为调整之后的内存起始位置。
⑤这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
Ⅰ:情况1:原有空间之后有足够大的空间
Ⅱ:情况2:原有空间之后没有足够大的空间
这里是引用

情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。
当然口头是上的讲解总是晦涩难懂的,我们来看例子。
例题:

int main()
{
    int* ptr = (int*)malloc(100);
    if (ptr != NULL)
    {
        //业务处理
    }
    else
    {
        exit(EXIT_FAILURE);
    }
    //扩展容量
    //代码1
    ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
    //没有判断是否会开辟失败
    //代码2
    int* p = NULL;
    p = realloc(ptr, 1000);//调整空间
    if (p != NULL)//判断是否会开辟成功
    {
        ptr = p;
    }
    //业务处理
    free(ptr);
    return 0;
}

来吧看看讲解,把它分为俩总情况:
在这里插入图片描述

常见的动态内存错误

对NULL指针的解引用操作

void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题
 //因判断是否为空指针,不然就是对空指针的赋值
 free(p);
}

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

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

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

void test()
{
 int a = 10;
 int *p = &a;
 free(p);//只能对不是动态内存的空间进行释放
}

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

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置,只释放了部分空间
}

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

void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放,对同一块空间进行了重复的释放
}

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

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
	    *p = 20;
	}
}
int main()
{
    test();//在调用这个函数时,空间未得到及时释放,可能会导致内存泄露
	while (1);
}

几个经典的笔试题

例题:请问运行Test 函数会有什么样的结果?

void GetMemory(char* p)
{
	p = (char*)malloc(100);//没有及时释放内存
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);//此时str仍然是空指针
	strcpy(str, "hello world");//对空指解引用,导致程序崩溃
	printf(str);
}
int main()
{
	Test();
	return 0;
}

代码分析:

各位来看图,我们传过去的是str(即str指向的那块空间),此时p接受的就是这一块空间,对这块空间的引用与扩容并没有真的是使&str这块空间的地址增加,而是让NULL这块空间增加,因此我们的str始终指向的是NULL这块空间这里是引用

对它进行正确的修改

//代码的修改
void GetMemory(char** p)//用二级指针来接受一级指针的地址
{
	*p = (char*)malloc(100);//将str的地址指向开辟的空间
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

例题:请问运行Test 函数会有什么样的结果?

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}

代码分析:

随着GetMemory函数的销毁,p所指向的那块空间也被销毁,此时p变成了野指针,因此str就是对野指针的引用

例题:请问运行Test 函数会有什么样的结果?

void GetMemory(char **p, int num)
{
 *p = (char *)malloc(num);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(&str, 100);
 strcpy(str, "hello");
 printf(str);
}

代码分析:

在开辟完空间后,与使用该空间,没有及时释放该空间,而导致的内存泄漏。

今天的内容就讲到这里了,如果有讲的不好的地方希望各位多多指出。

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

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

相关文章

【Linux】 du 命令使用

问题 No space left on device 请求接口返回 java.io.IOException: No space left on device 设备上没有剩余空间 怎么解决问题&#xff1a; 查看这篇文章&#xff1a;一次 linux 服务器磁盘使用情况排查 我们提到命令&#xff1a;du -sh * 到底这个命令是干什么的咱们…

EXPLAIN 语句输出的各个列解释

title: “EXPLAIN 语句输出的各个列解释” createTime: 2022-03-06T15:52:4108:00 updateTime: 2022-03-06T15:52:4108:00 draft: false author: “ggball” tags: [“mysql”] categories: [“db”] description: “” EXPLAIN 语句输出的各个列解释 列名描述id在一个大的查…

多线程(基础)

文章目录 1. 线程的声明周期1.1 JDK 中用 Thread.State 枚举表示了线程的几种状态1.2 线程状态转换图 2. 线程的同步2.1 Synchronized 线程同步机制 3. 互斥锁3.1 注意事项和细节3.2 守护线程 setDaemon()方法 4. 线程的死锁5. 释放锁6. 课后练习 1. 线程的声明周期 1.1 JDK 中…

四通道信息融合下的齿轮箱故障诊断(Python代码,SVM模型和CNN模型进行对比实验,解压缩即可运行,有详细中文注释)

1.效果运行视频&#xff1a;四通道信息融合下的齿轮箱故障诊断&#xff08;Python代码&#xff0c;SVM模型和CNN模型进行对比实验&#xff09;_哔哩哔哩_bilibili 用到的库&#xff1a; 2.数据集介绍&#xff1a;数据免费下载链接&#xff08;不要积分&#xff09;&#xff1a…

什么是跨站请求伪造(CSRF)攻击?如何防止它?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是跨站请求伪造&#xff08;CSRF&#xff09;攻击&#xff1f;⭐ 如何防止CSRF攻击&#xff1f;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦…

xxl-job 2.2之后版本高版本executor未授权访问漏洞

xxl-job 低版本executor未授权访问 低版本的executor未授权访问漏洞是 POST /run HTTP/1.1 Host: your-ip:9999 Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like G…

26663-2011 大型液压安全联轴器 课堂随笔

声明 本文是学习GB-T 26663-2011 大型液压安全联轴器. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了大型液压安全联轴器的分类、技术要求、试验方法及检验规则等。 本标准适用于联接两同轴线的传动轴系&#xff0c;可起到限制…

基于Linux 系统聊天室登录与注册实现(03)

上一篇我们已经讲了如何搭建一个多线程的服务器模型&#xff0c;可以支持多个客户端同时连接服务器&#xff0c;本篇我们来实现多个客户端&#xff0c;如何实现向服务器注册信息&#xff0c;并实现登录的功能。 数据结构 接着上一篇的实例代码继续增加功能。要实现注册和登录…

(Java)关于easyExcel合并单元格

今天过客遇到一个需要合并单元格的业务&#xff0c;但是之前过客用的一直是easyExcel框架&#xff0c;所以这次也不想去使用其他的框架&#xff0c;今天就跟大家讲讲easyExcel怎么进行单元格的合并。 首先使用easyExcel进行导出的实体类一样是依据之前那样写&#xff0c;之后在…

宠物玩具在欧洲销售CE认证EN71测试标准

CE认证的EN71测试宠物玩具办理&#xff1a; 宠物玩具是用来给宠物玩耍&#xff0c;基于将宠物作为人类伙伴关系而诞生的一种玩具类型&#xff0c;这种玩具的存在就是让人类和自己的宠物真正的互动起来&#xff0c;在情感上面得到更大的交流和互动。 那么宠物玩具出口到欧盟市场…

支付宝电脑网站支付,异步通知

一&#xff1a;异步通知是支付宝回调商户的服务器&#xff0c;所以这个地址需要通过外网访问&#xff0c;在真实项目中都会有对应的服务器&#xff0c;但是在测试中只有使用内网穿透工具 推荐使用NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 配置好内网穿透之后不要忘记…

目标检测如何演变:从区域提议和 Haar 级联到零样本技术

目录 一、说明 二、目标检测路线图 2.1 路线图&#xff08;一般&#xff09; 2.2 路线图&#xff08;更传统的方法&#xff09; 2.3 路线图&#xff08;深度学习方法&#xff09; 2.4 对象检测指标的改进 三、传统检测方法 3.1 维奥拉-琼斯探测器 (2001) 3.2 HOG探测器…

最大内切圆算法计算裂缝宽度

本文这里是对CSDN上另一位博主的代码进行了整理&#xff1a; 基于opencv的裂缝宽度检测算法&#xff08;计算轮廓最大内切圆算法&#xff09; 我觉得这位博主应该是上传了一个代码草稿&#xff0c;我对其进行了重新整理&#xff0c;并添加了详细的注释。 import cv2 import …

产业园区中工业厂房的能源综合配置

安科瑞 崔丽洁 园区工业地产中能源综合配置存在的问题 我国园区工业地产建设已历经近40年的发展, 园区在区域经济发展、产业集聚方面发挥了重要的载体和平台作用, 有力推动了我国社会经济的高质量发展。园区工业地产是国民经济的发展的重要载体, 但同时也是集中的环境污染源。…

大数据Doris(一):Doris概述篇

文章目录 Doris概述篇 一、前言 二、Doris简介

Norms and Inner Products

See https://ai.stanford.edu/~gwthomas/notes/norms-inner-products.pdf

Jenkins 权限管理

关于Role-based Authorization Strategy 使用Jenkins自身的权限管理过于粗糙&#xff0c;无法对单个、一类项目做管理&#xff0c;我们可以使用 Role-based Authorization Strategy插件来管理项目、角色。 首先安装该插件&#xff1a;在Jenkins查看该插件有无安装 在Jenkins-…

C++ 类和对象 (5) 析构函数

用构造函数创建对象后&#xff0c;程序负责跟踪该对象&#xff0c;直到对象过期为止。对象过期时&#xff0c;程序将自动调用一个特殊的成员函数&#xff0c;该函数的名称——析构函数。析构函数完成清理工作&#xff0c;实际上还是很有用的。例如&#xff0c;用new来分配一个构…

八、【漏洞复现】jupyter-notebook 命令执行(CVE-2019-9644)

8.0、基础知识 1、测试功能点 &#xff08;这种情况基本上很难遇到&#xff09; 8.1、漏洞原理 ​Jupyter Notebook是一套用于创建、共享代码和说明性文本文档的开源Web应用程序。 Jupyter Notebook可直接使用命令行执行任意命令。​ 8.2、影响范围 未授权开启终端权限的…

嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序

嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序 第七章 具体单板的 LED 驱动程序7.1 怎么写 LED 驱动程序&#xff1f;7.2 AM335X的 LED驱动程序7.2.1 原理图 XXXXXX_AM335X开发板结构为&#xff1a;7.2.2 所涉及的寄存器操作7.2.3 写程序7.2.4 配置内核去掉原有…