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

devtools/2024/10/15 20:24:23/

本章概述

  • 为什么要有动态内存分配?
  • 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;elsep = 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;elsep = 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.ppmy.cn/devtools/121334.html

相关文章

基于多维统计分析与GMM聚类的食品营养特征研究

1.项目背景 在当今社会&#xff0c;随着人们对健康和营养的日益关注&#xff0c;深入了解食品的营养成分及其对人体的影响变得越来越重要&#xff0c;本研究采用了多维度的分析方法&#xff0c;包括营养成分比较分析、统计检验、营养密度分析和高斯混合模型&#xff08;GMM&am…

Debian 配置 Python 开发与运行环境

配置 Python 开发与运行环境。 1.3.1. Debian下的安装与配置 Debian 是一个致力于自由软件开发并宣扬自由软件基金会理念的自愿者组织。 Debian 计划创建于 1993 年。当时&#xff0c;Ian Murdock 发出一份公开信&#xff0c; 邀请软件开发者们参与构建一个基于较新的 Linux …

(9)MATLAB瑞利衰落信道仿真2

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、瑞利衰落信道二、瑞利衰落信道建模三、仿真结果二、高斯随机变量和瑞利随机变量后续 前言 本文首先给出瑞利衰落信道模型&#xff0c;并根据瑞利衰落变量估…

安装配置pytorch(cuda、、cudnn、torch、torchvision对应版本)

参考&#xff1a; Pytorch环境配置——cuda、、cudnn、torch、torchvision对应版本&#xff08;最全&#xff09;及安装方法_cuda12.2对应的pytorch版本-CSDN博客 https://download.pytorch.org/whl/torch_stable.html Previous PyTorch Versions | PyTorch

关于 JVM 个人 NOTE

目录 1、JVM 的体系结构 2、双亲委派机制 3、堆内存调优 4、关于GC垃圾回收机制 4.1 GC中的复制算法 4.2 GC中的标记清除算法 1、JVM 的体系结构 "堆"中存在垃圾而"栈"中不存在垃圾的原因: 堆(Heap) 用途:堆主要用于存储对象实例和数组。在Java中…

封装提示词翻译组件

一、本章诉求 由于前期设计的提示词均是英文输入&#xff0c;后期用户要求增加中文提示词输入&#xff0c;这个需求更改有两个方法&#xff0c;一个是修改comfyui工作流&#xff0c;另一个是修改前端&#xff0c;在前端将用户输入的中文翻译成英文&#xff0c;再推送到工作流中…

【Element-UI】实现el-drawer抽屉的左右拖拽宽度

对Element-UI的el-drawer抽屉控件实现拖拽功能。 1、新增drawer-drag.js import Vue from vueVue.directive(drawerDrag, {bind(el, binding, vnode, oldVnode) {const minWidth 400const dragDom el.querySelector(.el-drawer)dragDom.style.overflow autoconst resizeElL…

LP3718BSL封装SOP8/12W隔离开关电源芯片

概述: LP3718BSL 是一款高度集成的隔离型适配器和充电器的自供电PSR控制芯片&#xff0c;外围设计极其简单。 LP3718BSL通过外置电阻&#xff0c;可调原边峰值电流&#xff0c;再 通过变压器原副边匝比来设置输出恒流点&#xff1b;通过设 定 FB 上偏电阻和下偏电阻来设置输出恒…