C语言:动态内存管理

目录

为什么存在动态内存分配

动态内存函数

malloc和free

示例

calloc

示例

realloc 

示例

常见的动态内存错误

对NULL指针的解引用操作

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

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

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

对同一块内存多次释放

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

经典样例

Demo——程序崩溃,造成内存泄漏

原因

解决方案

Demo——野指针,随机打印值

原因

Demo——程序崩溃,内存泄漏

原因

解决方案


 

为什么存在动态内存分配

原本的内存开辟方式有:

int val = 20 在栈空间上开辟四个字节
char arr [ 10 ] = { 0 };  在栈空间上开辟 10 个字节的连续空间
但是上述的开辟空间的方式有两个特点:
  1. 空间开辟大小是固定的。
  2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

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

动态内存函数

malloc和free

C 语言提供了一个动态内存开辟的函数:
void* malloc ( size_t size );
这个函数向内存申请一块 连续可用 的空间,并返回指向这块空间的指针。
  1. 如果开辟成功,则返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  3. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  4. 如果参数 size 0malloc的行为是标准是未定义的,取决于编译器

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下: 

void free ( void* ptr );
free 函数用来释放动态开辟的内存。
  1. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  2. 如果参数 ptr NULL指针,则函数什么事都不做。
  3. mallocfree都声明在 stdlib.h 头文件中。

示例


    int arr[10] = {0};          // 默认这种开辟的空间是在栈区的,栈区用于存放临时变量
    int *p = (int *)malloc(40); // 动态内存开辟,开辟40个字节,返回为void *类型,动态开辟的是存放在 堆区
    if (p == NULL)              // 开辟失败返回空指针
    {
        printf("%s\n", strerror(errno));
        return -1;
    }
    for (int i = 0; i < 10; i++)
    {
        *(p + i) = i;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d", *(p + i)); // 0 1 2 3 4 5 6 7 8 9
    }

    free(p);  // 先释放p指向的空间
    p = NULL; // 后此时p是空指针,再也找不到上面动态开辟的内存空间

calloc

C语言还提供了一个函数叫 calloc calloc 函数也用来动态内存分配。原型如下:

void* calloc ( size_t num , size_t size );
  1. calloc函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  2. 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

示例

    int *p = (int *)calloc(10, sizeof(int));
    if (p == NULL)
    {
        printf("%s\n", strerror(errno));
        return -1;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d\n", *(p + i)); // 10个0
    }
    free(p);  // 释放
    p = NULL; // 置空

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

realloc 

  • realloc函数的出现让动态内存管理更加灵活。
  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
  • realloc(NULL, 40) == malloc(40) 两者等价
函数原型如下:
void* realloc ( void* ptr , size_t size );
  1. ptr 是要调整的内存地址
  2. size 调整之后新大小
  3. 返回值为调整之后的内存起始位置。
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 的空间。
  5. realloc在调整内存空间的是存在两种情况:
    1. 情况1原有空间之后有足够大的空间
    2. 情况2:原有空间之后没有足够大的空间
情况 1
当是情况 1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况 2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。

示例

    int *p = (int *)malloc(40);
    if (p == NULL)
    {
        printf("%s\n", strerror(errno));
        return -1;
    }
    for (int i = 0; i < 10; i++)
    {
        *(p + i) = i + 1; // 此时p指向的数组的值是 1~10
    }
    /*
        *realloc扩容:从原本40字节开辟至80字节
        之所以不直接用p指针接收,是因为realloc可能扩容失败返回空指针,原本p指针还是好好地指向40个字节的数组,这下没扩容,反到给p赋值为了空指针
     */
    int *ptr = (int *)realloc(p, 80);
    if (ptr != NULL)
    {
        p = ptr;
    }
    for (int i = 0; i < 10; i++) // 验证
    {
        printf("%d ", *(p + i)); // 1~10
    }

常见的动态内存错误

对NULL指针的解引用操作

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

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

        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释放

        int a = 10;
        int *p = &a;
        free(p);//err

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

 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置

对同一块内存多次释放

        int *p = (int *)malloc(100);
        free(p);
        free(p);//重复释放

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

经典样例

Demo——程序崩溃,造成内存泄漏

void GetMemory(char *p)
{
    p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}
原因

      因为:str空指针传入GetMemory函数后,形参相当于声明了一个临时p指针用于接收str指针变量,并开启了100字节的动态内存出函数后,临时变量p指向的空间会被销毁,而动态内存所开辟的内存空间依然还在,str依旧是空指针,这就造成后续 strcpy方法 将 "hello world"拷贝到str会崩溃(方法内部会对str进行解引用,NULL解引用会崩溃)

解决方案
void GetMemory(char **p) // 二级指针接收指针的地址
{
    /* 解引用得到str指针变量,也就是对str所指向的空间动态开辟了100个字节的内存 */
    *p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(&str); // 传递str指针变量的地址
    /* 此时str所指向的地址已经有了100个字节的空间,内部解引用,对str赋值字符串 hello world */
    strcpy(str, "hello world");
    printf(str); // hello world 打印方式等价于 printf("%s", str); 这种等价不可用于str[]的方式
    /* 释放动态内存 */
    free(str);
    str = NULL;
}

Demo——野指针,随机打印值

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

   因为p是临时指针变量,出函数,p销毁,所指向的空间被回收或者别占用,p所指向的地址已经不属于p了,当再去解引用,会有随机值。哦,当前都编译不通过 gcc

Demo——程序崩溃,内存泄漏

        void Test(void)
        {
            char *str = (char *) malloc(100);
            strcpy(str, "hello");
            free(str);
            if(str != NULL)
            {
                strcpy(str, "world");
                printf(str);
            }
        }
原因

    因为:free释放动态开辟的内存后,str所指向的空间已经被释放,但是str的地址还是原来的那个地址,并不是空指针进入if后,对不是自己的空间的地址解引用,str也就是野指针了,属于非法访问,程序崩溃

解决方案
void Test_three(void)
{
    char *str = (char *)malloc(100);
    strcpy(str, "hello");
    free(str);
    str = NULL;
    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

lv11 嵌入式开发 ARM指令集中(伪操作与混合编程) 7

目录 1 伪指令 2 伪操作 3 C和汇编的混合编程 4 ATPCS协议 1 伪指令 本身不是指令&#xff0c;编译器可以将其替换成若干条等效指令 空指令NOP 指令LDR R1, [R2] 将R2指向的内存空间中的数据读取到R1寄存器 伪指令LDR R1, 0x12345678 R1 0x12345678 LDR伪指令可以将任…

深度学习:欠拟合与过拟合

1 定义 1.1 模型欠拟合 AI模型的欠拟合&#xff08;Underfitting&#xff09;发生在模型未能充分学习训练数据中的模式和结构时&#xff0c;导致它在训练集和验证集上都表现不佳。欠拟合通常是由于模型太过简单&#xff0c;没有足够的能力捕捉到数据的复杂性和细节。 1.2 模型…

mysql练习1

-- 1.查询出部门编号为BM01的所有员工 SELECT* FROMemp e WHEREe.deptno BM01; -- 2.所有销售人员的姓名、编号和部门编号。 SELECTe.empname,e.empno,e.deptno FROMemp e WHEREe.empstation "销售人员";-- 3.找出奖金高于工资的员工。 SELECT* FROMemp2 WHE…

SpringSecurity6 | 默认登录页

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java从入门到精通 ✨特色专栏&#xf…

SpringBoot项目连接linux服务器数据库两种解决方法(linux直接开放端口访问本机通过SSH协议访问,以mysql为例)

最近找个springboot脚手架重新熟悉一下springboot相关框架的东西&#xff0c;结果发现好像项目还不能直接像数据库GUI工具一样填几个SSH参数就可以了&#xff0c;于是就给他再整一下看看如何解决 linux开放3306&#xff08;可修改&#xff09;端口直接访问 此方法较为方便&am…

基于一致性算法的微电网分布式控制MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 本模型主要是基于一致性理论的自适应虚拟阻抗、二次电压补偿以及二次频率补偿&#xff0c;实现功率均分&#xff0c;保证电压以及频率稳定性。 一致性算法 分布式一致性控制主要分为两类&#xff1a;协调同…

ZJU Beamer学习手册(二)

ZJU Beamer学习手册基于 Overleaf 的 ZJU Beamer模板 进行解读&#xff0c;本文则基于该模版进行进一步修改。 参考文献 首先在frame文件夹中增加reference.tex文件&#xff0c;文件内容如下。这段代码对参考文献的引用进行了预处理。 \usepackage[backendbiber]{biblatex} \…

学习网络编程No.10【深入学习HTTPS】

引言&#xff1a; 北京时间&#xff1a;2023/11/14/18:45&#xff0c;因为种种原因&#xff0c;上个月的文章昨天才更新&#xff0c;目前处于刷题前夕&#xff0c;算法课在看了。这次和以前不一样&#xff0c;因为以前对知识框架没有很好的理念&#xff0c;并不清楚相关知识要…

从一到无穷大 #19 TagTree,倒排索引入手是否是优化时序数据库查询的通用方案?

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 文章主旨时序数据库查询的一般流程扫描维度聚合时间聚合管控语句 TagTree整体结构索引…

安装2023最新版PyCharm来开发Python应用程序

安装2023最新版PyCharm来开发Python应用程序 Install the Latest JetBrains PyCharm Community to Develop Python Applications Python 3.12.0最新版已经由其官网python.org发布&#xff0c;这也是2023年底的最新的版本。 0. PyCharm与Python 自从1991年2月20日&#xff0…

力扣刷题篇之位运算

系列文章目录 目录 系列文章目录 前言 一、位运算的基本运算 二、位运算的技巧 三、布隆过滤器 总结 前言 本系列是个人力扣刷题汇总&#xff0c;本文是数与位。刷题顺序按照[力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 - 力扣&#xff08;LeetCode&#xff0…

第7天:信息打点-资产泄漏amp;CMS识别amp;Git监控amp;SVNamp;DS_Storeamp;备份

第7天&#xff1a;信息打点-资产泄漏&CMS识别&Git监控&SVN&DS_Store&备份 知识点&#xff1a; 一、cms指纹识别获取方式 网上开源的程序&#xff0c;得到名字就可以搜索直接获取到源码。 cms在线识别&#xff1a; CMS识别&#xff1a;https://www.yun…

Android图片涂鸦,Kotlin(1)

Android图片涂鸦&#xff0c;Kotlin&#xff08;1&#xff09; import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Path import android.graphics.PointF import android.…

JAVA多线程(5)

JAVA多线程(5) 线程安全问题概述 卖票问题分析 单窗口卖票 一个窗口(单线程)卖100张票没有问题 单线程程序是不会出现线程安全问题的 多个窗口卖不同的票 3个窗口一起卖票,卖的票不同,也不会出现问题 多线程程序,没有访问共享数据,不会产生问题 多个窗口卖相同的票 3个窗口…

度加创作工具 演示

度加创作工具 功能图功能测试文比润色测试经验分享测试测试输出测试输出工具地址功能图 功能测试 文比润色测试 经验分享测试 测试输出 在人工智能领域,我们一直在追求一个终极目标:让机器能够像人类一样,能够理解、学习和解决各种复杂问题。而要实现这个目标,我们需要将…

【Java 进阶篇】JQuery 事件绑定之事件切换:让页面动起来

欢迎来到这个充满动感的 JQuery 事件绑定之旅&#xff01;在这篇博客中&#xff0c;我们将深入研究 JQuery 中的事件切换&#xff0c;让你的页面焕发出活力和互动。无论你是前端小白还是有一定经验的开发者&#xff0c;相信这篇文章都会对你有所帮助。 走进事件切换的奇妙世界…

python中Thread实现多线程任务

目录 多线程概括&#xff1a; 使用 Thread 模块创建线程 如果不使用多线程&#xff1a; 多线程概括&#xff1a; 多线程是一种并发执行的编程方式&#xff0c;允许程序同时执行多个独立的线程&#xff0c;每个线程在程序中运行独立的任务。每个线程都是程序的基本执行单元&a…

坐标系下的运动旋量转换

坐标系下的运动旋量转换 文章目录 坐标系下的运动旋量转换前言一、运动旋量物体运动旋量空间运动旋量 二、伴随变换矩阵三、坐标系下运动旋量的转换四、力旋量五、总结参考资料 前言 对于刚体而言&#xff0c;其角速度可以写为 ω ^ θ ˙ \hat {\omega} \dot \theta ω^θ˙&…

【2017年数据结构真题】

请设计一个算法&#xff0c;将给定的表达式树&#xff08;二叉树&#xff09;转换成等价的中缀表达式&#xff08;通过括号反映次序&#xff09;&#xff0c;并输出。例如&#xff0c;当下列两棵表达式树作为算法的输入时&#xff1a; 输出的等价中缀表达式分别为(ab)(a(-d)) 和…