C语言高阶【1】--动态内存管理【1】(可以灵活的申请和使用内存,它不香吗?)

news2024/11/23 20:43:48

本章概述

  • 为什么要有动态内存分配?
  • malloc函数和free函数
  • calloc函数和realloc函数
  • 常见的动态内存的错误
  • 彩蛋时刻!!!

为什么要有动态内存分配?

  • 情况描述:当我们创建一个变量时,比如,int i=0 ;内存就直接分配一块空间,这个空间是固定的。再比如,我们创建一个数组char arr[10];内存就直接分配了一块连续的空间,这个空间也是固定的。当我们要存放的数据过大时,原先申请的空间就不能满足了,就要重新创建变量,重新申请空间了,这样做很麻烦的。又或者,当我们的数据较小时,就会有用不到的空间,就会浪费掉。总之,对于空间的使用和申请不是很灵活,很不方便
  • 动态内存的介绍:上面咱们讲了内存申请的不灵活和不方便。动态内存就是解决这个问题的,因为有了动态内存,我们就可以想申请多少空间就可以申请多少的空间,甚至中间还能加或减少空间。这就使得我们申请内存空间很灵活了。关于内存,咱们之前讲过了,内存划分为栈区,堆区和静态区函数参数,局部变量,结构体和数组这些都在栈区创建和开辟空间的malloc,free,callocrealloc这些动态内存函数是在堆区全局变量和常量这些是在静态区。所以,我们用动态内存函数是在堆区进行空间申请和创建的
  • 动态内存的缺点:动态内存对于内存的申请很灵活,这就导致了很容易出错。因为我们对于内存申请的权限变大了,对于我们管理内存的能力就要提高了,能力不够的话,很容易出错的。
  • 注意:这些动态内存函数的头文件<stdlib.h>.

malloc函数和free函数

关于动态内存的学习,我们把malloc,free,callocrealloc这几个内存函数掌握住就足够了。

  • malloc函数malloc函数是用来开辟内存的函数。我们先来看一下它的结构:
void* malloc (size_t size);
1.size_t size表示你要申请多少个字节的空间。
2.这个函数开辟的是一块连续的空间,和数组开辟的空间是一样的,都是连续的。
3.返回值是void*的指针,当这个函数开辟好空间后,就会返回这个空间的起始地址。
因为你要存的数据类型是不确定的,所以返回void*的指针。所以,当我们使用时,要强制类型转换。
  • malloc函数的使用:我们开辟个4个int型的空间,进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(4*sizeof(int));
	return 0;
}

结果运行图:在这里插入图片描述
从结果运行图就可以看出来,开辟了一块连续的空间。我们就可以往这个空间里面存入一些数据了。前面,咱们讲过了,动态内存函数开辟的是一块连续的空间和数组开辟的方式一样。所以,我们往里面存值和取值的方式和数组是一样的。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(4*sizeof(int));
	int i = 0;
	for (i = 0; i < 4; i++)
	{	
		*(p + i) = i + 1;  //存入数据1~4
	}
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

结果运行图:在这里插入图片描述
我们还可以用数组的操作符' [ ]'进行访问,进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(4 * sizeof(int));
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		p[i] = i + 1;  //存入数据1~4
	}
	for (i = 0; i < 4; i++)
	{
		printf("%d ",p[i]);
	}
	return 0;
}

结果运行图:在这里插入图片描述
对于动态内存函数开辟的空间的使用,我们完全可以类比数组的使用方式

  • malloc函数使用注意事项
    • 1.如果开辟空间成功,就会返回这个空间的起始地址
    • 2.如果开辟空间失败,就会返回NULL,所以一定要做这个函数返回值的检查
    • 3.如果这个函数的参数为0,这个函数具体怎么做是标准未定义的,取决于编译器,在VS中,就什么也不做
  • free函数的介绍free函数具有释放内存的作用。当我们创建的空间不再使用时,就要释放内存空间。这就好比,咱们从图书馆里面借书。如果你只借不换的话,图书馆里面的书总有一天就空了,而且你一直借着,别人也借不了。内存也是这个道理。
  • free函数的使用:我们先来看这个函数的结构:
void free (void* ptr);
1.这个函数无返回值。
2.这个函数的参数是个指针类型,我们需要把我们开辟的空间起始地址传给free函数,才能释放空间

进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(4 * sizeof(int));
	free(p);
	p = NULL;
	return 0;
}

结果运行图:创建的空间。在这里插入图片描述
释放后空间:在这里插入图片描述

  • 使用注意事项:
    • 1.free函数不可以释放栈区和静态区的变量空间,只能释放堆区(动态内存)的空间。进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int i = 0;
	free(&i);
	return 0;
}

结果运行图:在这里插入图片描述

  • 2.free释放空间后,指针变量要立马赋予NULL,防止成为野指针(关于野指针的成因咱们讲过了)。当我们释放完空间后,虽然我们的指针变量里面还存放着那块内存的地址(未赋予NULL前)。但是,我们已经没有访问那块空间的权限了,free之后就把这个关系给嘎断了。如图所示:在这里插入图片描述
    所以,我们一般都会申请内存函数和free函数一起使用

calloc函数和realloc函数

  • calloc函数的介绍这个函数也是申请内存的函数,和malloc类似。但是,这个函数可以指定申请的空间数目和申请的空间大小。进行结构展示:
 void* calloc (size_t num, size_t size);
 这个函数表示你要申请多少num,空间大小为size的空间。
  • calloc函数的使用:进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int i = 0;
	int* p = (int*)calloc(5,4); //申请5个空间大小为4个字节的空间
	if (p ==NULL)
		return 1;
	for (i = 0; i < 5; i++)
	{
		p[i] = i + 1;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

结果运行图:在这里插入图片描述

  • malloc函数和calloc函数的区别这俩的功能是一样的。但是,它们俩的唯一(主要)区别就是:malloc函数开辟好空间后,不会初始化。calloc函数开辟好空间后,会初始化。进行结果图的展示–这里只展示calloc的图,malloc的图上面展示过了:在这里插入图片描述
  • realloc函数的介绍这个函数的功能就是在原来开辟的空间上进行空间大小的更改。比如,当我们觉得空间小了,我们就可以在原来空间的基础上扩大空间。或者我们开辟的空间太大了,我们就可以在原来的基础上减少空间。进行结构展示:
void* realloc (void* ptr , size_t size) ;
1.ptr是原来空间的起始地址,因为我们是在原来的空间基础上更改空间的,所以要知道原来空间的地址
2.size表示我们要更改后的空间大小。
  • realloc函数的使用:进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)calloc(5, 4); //申请5个空间大小为4个字节的空间
	int* pp = (int*)realloc(p, 3*sizeof(int));   //把空间缩小到3个int型的空间大小
	if (pp == NULL)
		return 1;
	else
		p = pp;
	free(p);
	p = NULL;
	return 0;
}

结果运行图:
在这里插入图片描述
在这里插入图片描述

  • ```realloc```的返回值注意(限于扩大空间):关于它的返回值要有两点的注意:
    • 1.当我们在原来空间的基础上开辟空间时,如果后面的空间足够时,就返回原来的空间地址
    • 2.当后面的空间不足时,realloc就会重新再找一块空间,进行新的空间开辟,然后返回新的空间地址。它先把原来的数据拷贝到新的空间里面,然后把原来的空间给释掉。如图所示:在这里插入图片描述
    • 3 .因为realloc函数有可能返回新的地址,但是空间开辟不足时就会返回NULL。如果,我们直接用同一个指针来接收返回值时,如果返回的是NULL,那么指针内容就会被更改,原来的空间也无法访问了。返回的不是NULL还好,就怕万一返回的是NULL。为了出现这种情况,我们要做个判断和中间变量。进行代码展示:
    int* p = (int*)calloc(5, 4); //申请5个空间大小为4个字节的空间
	int* pp = (int*)realloc(p, 3*sizeof(int));   //把空间缩小到3个int型的空间大小
	if (pp == NULL)
		return 1;
	else
		p = pp;

常见的动态内存的错误

  • 1.对NULL指针的解引用操作,进行代码展示:
void test()
 {
        int *p = (int *)malloc(INT_MAX/4);
        *p = 20;      //如果p的值是NULL,就会有问题
        free(p);
 }

当我们开辟的空间过大时,就可能会返回NULL,一旦p被赋予NULL,我们就无法访问。在之间加个判断部分就OK了。

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

当我们访问的空间超过了我们原本的空间大小,就会发生错误。

  • 3.对非动态开辟内存使用free释放,进行代码展示:
void test()
{
     int a = 10;
     int *p = &a;
    free(p);          //ok?
}

咱们讲过了,free只能释放动态内存函数开辟的空间,其它情况都不能释放。

  • 4.使用free释放一块动态开辟内存的一部分,进行代码展示:
void test()
{
     int *p = (int *)malloc(100);
      p++;
     free(p);      //p不再指向动态内存的起始位置
}

我们开辟多少的空间,就要释放多少的空间,所以要给起始地址。否则就报错。

  • 5.对同一块动态内存多次释放,进行代码展示:
void test()
{
    int *p = (int *)malloc(100);
     free(p);
     free(p);       //重复释放
}

这个很好理解,我们已经释放过内存了,干嘛还要继续释放同一块空间呢,这不是纯做没用功吗。

  • 6.动态开辟内存忘记释放(内存泄漏),进行代码展示:
void test()
{
      int *p = (int *)malloc(100);
     if(NULL != p)
     {
         *p = 20;
     }
}
int main()
{
    test();
    while(1);
}

我们在使用完动态内存函数开辟的空间后,记住释放空间,也就是说动态内存函数和free是配套用的。

  • 补充
    • 1.咱们说过,用完内存后就要释放。其实有时候,当我们忘记释放内存的时候,程序运行结束后,会自动释放内存的。但是,咱们还是要养成随用随释放的好习惯
    • 2.动态内存函数成功开辟空间后,会返回这个空间的起始地址,否则就返回NULL,所以要判断(检查)返回值

彩蛋时刻!!!

https://www.bilibili.com/video/BV15K41147o7/?spm_id_from=333.788.recommend_more_video.0&vd_source=7d0d6d43e38f977d947fffdf92c1dfad在这里插入图片描述
每章一句我要对自己有耐心,因为我知道这是成长需要的。感谢你能看到这里,点赞+关注+收藏+转发是对我最大的鼓励,咱们下期见!!!

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

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

相关文章

《中安未来护照阅读器 —— 机场高效通行的智慧之选》

在机场&#xff0c;高效与准确的旅客信息处理至关重要。中安未来护照阅读器&#xff0c;为机场带来全新的智能化体验。它能够快速准确地读取护照信息&#xff0c;自动识别多种证件类型&#xff0c;极大提高了值机、安检等环节的效率。无论是繁忙的国际航站楼&#xff0c;还是国…

51单片机的串口

目录 一、串口的介绍 1、硬件电路 二、51单片机的UART 1、串口参数及时序图 2、串口模式图 3、串口和中断系统结构图 4、串口相关寄存器 三、串口向电脑发送数据 1、通过STC-ISP软件 四、电脑通过串口控制LED 1、主函数 2、 UART串口通信模块 一、串口的介绍 串口是一…

倒排索引是什么

倒排索引 简单了解&#xff1a; 什么是正向索引? 基于文档id创建索引。查询词条时必须先找到文档&#xff0c;而后判断是否包含词条 什么是倒排索引? 对文档内容分词&#xff0c;对词条创建索引&#xff0c;并记录词条所在文档的信息。查询时先根据词条查询到文档id&#…

C++和OpenGL实现3D游戏编程【连载13】——多重纹理混合详解

🔥C++和OpenGL实现3D游戏编程【目录】 1、本节要实现的内容 前面说过纹理贴图能够大幅提升游戏画面质量,但纹理贴图是没有叠加的。在一些游戏场景中,要求将非常不同的多个纹理(如泥泞的褐色地面、绿草植密布的地面、碎石遍布的地面)叠加(混合)起来显示,实现纹理间能…

WPS(金山文档)与金蝶云星空通过HTTP实现连接

WPS(金山文档)通过HTTP与金蝶云星空实现数据互通 该方式不需要通过金蝶SDK webapi官方文档地址&#xff1a;https://vip.kingdee.com/article/407944297573586944?langzh-CN&productLineId1&isKnowledge2 一、两种方式 airscript脚本发送http请求和PY脚本编辑器发送…

SCoRe: 通过强化学习教导大语言模型进行自我纠错

大语言模型(LLMs)在推理任务中,如数学问题求解和编程,已经展现出了优秀的性能。尽管它们能力强大,但在实现能够通过计算和交互来改进其回答的算法方面仍然面临挑战。现有的自我纠错方法要么依赖于提示工程,要么需要使用额外的模型进行微调,但这些方法都有局限性,往往无法产生有…

Java项目实战II基于Java+Spring Boot+MySQL的海滨体育馆管理系统的设计与实现(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 随着健康意识的提升和体育运动的普及&#xff0c;海滨体育馆作为集休闲、健身、娱乐于一体的综合性场…

MongoDB伪分布式部署(mac M2)

1. 序言 本博客是上一博客的进阶版&#xff1a;mac M2安装单机版 MongoDB 7.x&#xff0c;上一博客可以看做是单机、单节点部署MongoDB本博客将介绍单机、多服务部署MongoDB&#xff0c;实际就是伪分布式部署 2. 副本集(Replica Set)方式部署 2.1 什么是副本集&#xff1f; …

DNS与ICMP

一、DNS 在TCP/IP协议中&#xff0c;要用IP地址和端口号来连接服务器&#xff0c;但是直接输入数字用户体验感不好&#xff0c;所以就发明了主机号&#xff08;字符串&#xff09;&#xff0c;用host文件作主机名与IP的映射关系储存。 随着数量的增多&#xff0c;手动管理太麻…

【机器学习】集成学习——提升模型准确度的秘密武器

【机器学习】集成学习——提升模型准确度的秘密武器 1. 引言 集成学习&#xff08;Ensemble Learning&#xff09;是一种通过结合多个弱模型来提升整体预测准确性的技术。通过将多个模型的预测结果进行组合&#xff0c;集成学习在复杂任务中展现了极强的泛化能力。本文将探讨…

基于微信的乐室预约小程序+ssm(lw+演示+源码+运行)

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…

深入探讨指令调优的局限性

深入探讨指令调优的局限性 摘要 指令调优&#xff08;Instruction Tuning, IT&#xff09;是通过使用指令-响应对训练大型语言模型&#xff08;LLMs&#xff09;的过程&#xff0c;已成为将基础预训练LLMs转化为开放领域对话代理的主要方法。尽管IT取得了显著的成功和广泛的应…

HUAWEI New4.9G 与 2.6G 无法正常切换问题处理案例

HUAWEI New4.9G 与 2.6G 无法正常切换问题处理案例 在某地市的 XX 音乐节保障准备期间&#xff0c;为确保活动期间的网络质量&#xff0c;现场新开了 4.9G HUAWEI 室外基站。在网络优化和测试中&#xff0c;发现UE无法实现从 2.6G 到 4.9G 的正常切换。虽然现场具备 4.9G信号覆…

算法与数据结构--二分查找

原理 简单说就是每次二分取中间值&#xff0c;然后将中间值与我们要找的值比较&#xff0c;如果比它大就移动左边界到其右侧&#xff0c;如果比它小就移动右边界到其左侧。直到中间值等于该值或者经过移动直至右边界在左边界左侧为止。 二.具体实现 public int searchInsert(…

数据结构 ——— 单链表oj题:反转链表

目录 题目要求 手搓一个简易链表 代码实现 题目要求 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表 手搓一个简易链表 代码演示&#xff1a; struct ListNode* n1 (struct ListNode*)malloc(sizeof(struct ListNode)); assert(n1);…

【重学 MySQL】五十三、MySQL数据类型概述和字符集设置

【重学 MySQL】五十三、MySQL数据类型概述和字符集设置 MySQL数据类型概述MySQL字符集设置注意事项 MySQL数据类型概述 MySQL是一个流行的关系型数据库管理系统&#xff0c;它支持多种数据类型&#xff0c;以满足不同数据处理和存储的需求。理解并正确使用这些数据类型对于提高…

已解决:org.springframework.web.HttpMediaTypeNotAcceptableException

文章目录 写在前面问题描述报错原因分析&#xff1a; 解决思路解决办法1. 确保客户端请求的 Accept 头正确2. 修改 Controller 方法的 produces 参数3. 配置合适的消息转换器4. 检查 Spring 配置中的媒体类型5. 其他解决方案 总结 写在前面 在开发过程中&#xff0c;Spring 框…

实验1 集成开发环境的使用及程序设计入门

1、求两点之间的距离 【问题描述】从键盘输入平面上已知两点的坐标A(x1,y1)、B(x2,y2)&#xff0c;计算两点之间的距离。结果保留4位小数。 【输入形式】两个点的坐标 【输出形式】两点之间的距离 【样例输入】 0 0 3 4 【样例输出】 5.0000 #define _CRT_SECURE_NO_WARNI…

(C语言贪吃蛇)12.Linux线程概念引入及编程实现

目录 前言 Linux线程概念 解决方法 注意事项⚠️ 解决问题&#x1f525; 总结 前言 我们上节提出了两个死循环不能同时运行&#xff0c;导致我们无法控制贪吃蛇的运动方向&#xff0c;本节我们便来解决这个问题。 Linux线程概念 线程是一个进程内部的控制序列&#xff0c…

鸿蒙应用开发前置学习-TypeScript

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…