「C语言进阶1」动态内存分配

目录

一、动态内存分配是什么?

二、为什么需要动态内存分配?

三、怎么进行动态内存分配?

1. malloc

2. calloc

3. realloc

a. realloc功能解析

b. 内存泄漏和内存块被截断问题

c. 总结

4. free 

四、使用动态内存分配常见的问题

【面试题】


一、动态内存分配是什么?

        动态内存分配是指在程序执行的过程中,系统根据程序的需要动态地分配或回收存储空间的分配内存的方法。

        这种分配方式不像数组等静态内存分配方法那样,需要预先指定要分配的内存大小,且一经分配后大小无法修改,而是由系统按程序的需要灵活地分配和回收内存空间。


二、为什么需要动态内存分配

        在C99引入变长数组前,定义数组时大小不能是变量,这就导致了开辟数组时必须指定数组的大小,且一经开辟,数组的大小再也无法改变。但实际情况下,如实现一个通讯录时,我们往往不知道需要多大的数组,且经常会遇到数组容量不够的情况,这就是静态内存分配的弊端。

        早期的解决方案就是使用动态内存分配,也就是使用四个库函数:malloc、calloc、 realloc、free对堆区的内存进行更灵活的分配和回收。

使用动态内存分配的优势:

  1. 可以灵活分配、回收内存:
            与大小固定的数组相比,动态内存分配可以使用malloc、calloc申请分配内存,使用relloc对申请好的内存空间大小进行调整,使用free回收内存。
  2. 可以随时回收,多次利用这部分空间:
            静态内存分配的空间,直到整个程序结束才会由系统自动释放。但是动态内存分配的空间需要用户手动释放,所以我们可以在使用完后立马通过free函数空间,使得这块空间能在一个程序中被反复使用,这样也能在一定程度上节省内存空间。
  3. 使用内存中堆区的空间
            动态内存分配是在堆上分配空间的,静态内存分配是在栈上分配空间的,所以了解动态内存分配后就能使用堆上的空间了。

三、怎么进行动态内存分配?

        C语言中动态内存管理方式是通过四个库函数实现的:malloc、calloc、 realloc、free。这四个库函数都在头文件<stdlib.h>

1. malloc

函数声明    |        void*  malloc (size_t  size);
功能      |        在堆上申请一个大小为size字节的空间。(申请分配动态内存)
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。
#include<stdio.h>
#include<stdlib.h>int main()
{// 开辟一个动态内存变量//malloc函数的返回值类型为void*所以要强制类型转换int* a = (int*)malloc(1 * sizeof(int));// 检查动态内存是否开辟失败,开辟失败返回NULLif (NULL == a){printf("动态内存开辟失败。\n");return 0;}// 使用动态内存*a = 1;printf("%d\n", *a);// 释放动态内存free(a);a = NULL; //free后指针仍指向原来的空间,所以要置为NULL
}
	// 开辟一个动态内存数组int size = 10; // 动态内存数组大小int* arr = (int*)malloc(size * sizeof(int));// 检查动态内存是否开辟失败if (NULL == arr){printf("动态内存开辟失败。\n");return 0;}// 使用动态内存数组(和静态内存数组的使用没有差别)for (int i = 0; i < size; ++i){arr[i] = i;printf("%d ", arr[i]);}// 释放动态内存数组free(arr);arr = NULL;

2. calloc

函数声明    |        void* calloc (size_t  num, size_t  size);
功能      |        在堆上申请大小为num个size字节的空间,并初始化空间中的内容为0。(分配动态内存)
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。
	// 使用calloc函数开辟一个动态内存变量/数组int size = 10; // 动态内存数组大小,size = 1即为变量int* arrc = (int*)calloc(size , sizeof(int));// calloc函数的返回值类型为void*if (NULL == arrc)// 检查{printf("动态内存开辟失败。\n");return 0;}for (int i = 0; i < size; ++i)// 使用{printf("%d ", arrc[i]); //calloc默认初始化内容全为0}free(arrc);// 释放arrc = NULL;

3. realloc

函数声明    |        void* realloc (void*  mem_address, unsigned int  newsize);
功能      |        见下方解析。(对申请好的动态内存空间大小进行调整,可以扩展或缩小动态内存。
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。

a. realloc功能解析

        realloc函数用于对申请好的动态内存空间大小进行调整。它接受两个参数:一个是已分配的动态内存的指针,另一个是要重新分配的大小(单位字节)。

        realloc函数首先检查传入的指针是否为空指针。如果是空指针,则等同于调用malloc函数,直接分配指定大小的动态内存,并返回指向该动态内存的指针。
        如果传入的指针不是空指针,则realloc函数会尝试重新分配内存块的大小。它会根据传入的新内存块大小newsize和原有内存块的大小oldsize来判断如何重新分配内存:

  • newsize = 0,释放原有的内存块,并返回空指针。
  • newsize > oldsize,则realloc函数会尝试在指针参数指向原有内存块的上进行扩容:
    如果原有内存块有足够的连续空间,直接扩容,并且将传入的指针返回;
    如果没有足够的连续空间,先按照指定的大小分配一个新的内存块,然后将原有数据拷贝到新内存块中,而后由系统自动释放原来的内存块,并返回新内存块起始位置的指针;
    如果扩容失败,则返回空指针,并且原有内存块的内容保持不变。
  • newsize < oldsize,则realloc函数会尝试缩小内存块的大小:
    它可能会直接使用缩小后的原内存块;
    也可能将原内存块中的数据复制到新内存块中,并返回指向新内存块的指针。内容不变,但是多余的数据可能会被截断。

b. 内存泄漏和内存块被截断问题

使用realloc函数时需要小心内存泄漏和内存块被截断的问题:

  • 如果realloc函数返回空指针,则说明内存分配失败,原内存块没有被释放,这可能会导致内存泄漏;(realloc失败不会释放原空间)
  • realloc函数可能会因为原来位置的内存大小不够,将原有内存块移到新的位置。但是,指向原有内存块的指针仍然指向原内存块,这可能会导致内存泄漏。所以realloc后要将原指针置为NULL。
  • 如果新的内存块大小小于原有内存块的大小,则可能会导致多余的数据被截断。

c. 总结

        realloc函数用于重新分配内存块的大小,可以扩展或缩小内存块。但是使用时需要注意内存泄漏和数据截断的问题。

	// 使用realloc函数调整动态内存的大小// 1.如果传入的是空指针,则相当于使用malloc开辟新空间:int* p1 = (int*)realloc(NULL, 1 * sizeof(int));if (NULL == p1){printf("动态内存开辟失败。\n");return 0;}*p1 = 1;printf("%d\n", *p1);free(p1);printf("p1所指向的空间:%d\n", sizeof p1);// 2.如果新空间需要的大小newsize = 0//则释放原有的内存块,并返回空指针。int* p2 = (int*)malloc(1 * sizeof(int));int* p3 = (int*)realloc(p2, 0);if (NULL == p3)printf("p2, p3所指向的空间将会由系统在合适的时间释放。\n");// 3.指针指向的旧空间大小oldsize < 新空间需要的大小newsize// 可能在原空间上扩容,也可能找一个新空间扩容int* p4 = (int*)calloc(10, sizeof(int));int* p5 = (int*)realloc(p4, 12 * sizeof(int));int* p6 = (int*)realloc(p5, 1000 * sizeof(int));if (p4 == p5)printf("在原空间上扩容。\n");if (p5 != p6)printf("找一个新空间扩容。\n");// 4.oldsize > newsize// 可能使用原空间,也可能找一个新空间int* p7 = (int*)calloc(10, sizeof(int));int* p8 = (int*)realloc(p7, 9 * sizeof(int));int* p9 = (int*)realloc(p8, 2 * sizeof(int));if (p7 == p8)printf("使用原空间。\n");if (p8 != p9)printf("找一个新空间。\n");

4. free 

函数声明    |        void free (void*  ptr);
        |        释放ptr指向的空间,如果ptr是空指针则不做处理;如果ptr指向的空间不是动态内存或是
功能      |        已经被释放的动态内存,则free函数的结果是未知的——可能导致程序崩溃,也可能导致
        |        内存泄漏。(回收动态内存)
返回值     |        无。

        释放动态内存,并不意味着执行到free函数时立马让该空间的内存全变为0,也不会让指针指向NULL,这只是告诉操作系统可以重新使用该内存块,具体的释放时间由操作系统决定。所以free后要手动将指针置为NULL。这也意味着无法通过指针是否指向NULL来判断空间是否已经被释放。

        注意,只有动态申请的堆区内存需要我们主动释放,其它区都由系统管理,强行free可能导致程序崩溃:


四、使用动态内存分配常见的问题

3.1 不检查动态内存是否开辟成功

如果开辟失败,返回值为NULL,不检查就可能会对NULL进行解引用操作。int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题free(p);

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

和静态数组一样,因忘记数组大小,导致越界访问。int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (int i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);

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

强行free非动态内存可能导致程序崩溃int t = 1;int* tp = &t;free(tp);return 0;

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

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

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

一样可能导致程序崩溃。int* p = (int*)calloc(10, sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}free(p);free(p);return 0;

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

        最常见的问题:忘记使用free函数释放动态内存空间,如果是一个长期运行的程序,这部分内存空间会因未释放而一直被占用,这就导致程序消耗的内存越来越多。切记: 动态开辟的空间一定要释放,并且正确释放 。

3.7 使用realloc和free后,未将原指针置为NULL

        realloc函数可能原位置空间不足,而使用新内存块移到新的位置。但是不会让指向原有内存块的指针不会指向NULL。free函数也不会让指针指向NULL。这可能会导致内存泄漏。所以realloc和free后要将原指针置为NULL。


【面试题】

题目1

void Test()
{
    int* p1 = (int*)malloc(sizeof(int));
    free(p1);
    // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof(int));
    int* p3 = (int*)realloc(p2, sizeof(int) * 10);
    // 2.这里需要free(p2)吗?
    free(p3);
}


1. malloc/calloc/realloc的区别?

        从参数和函数功能方面进行回答。


2.这里需要free(p2)吗?

        答:不需要,使用realloc扩容时,自动释放了p2之前的空间。

题目2

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


1. 请问运行Test 函数会有什么样的结果?

        答:运行Test函数会导致程序崩溃,因为形参只是实参的拷贝,形参的改变并不影响实参,所以str仍指向NULL,这会导致空指针异常。

题目3 

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


1. 请问运行Test 函数会有什么样的结果?

        答:不确定,因为在函数GetMemory执行完后,所处空间被系统销毁,p指向的空间也被销毁了,此时str因函数返回值指向了p指向的这片被消毁的空间。所以运行Test函数会产生不确定的结果。

题目4:

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


1. 请问运行Test 函数会有什么样的结果?

        答: 可能会导致程序崩溃,也可能会在显示屏上打印world。因为不是执行free函数就立即销毁指针指向的空间,指针也不会被free函数被置为NULL,所以指针仍指向原空间,如果系统还没销毁该空间,那么会在显示屏上打印world,否则会导致程序崩溃。


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins的使用GIT(4)

Jenkins的使用GIT 20211002 我们使用 Jenkins 集成外部 Git 仓库&#xff0c;实现对真实代码的拉取和构建。在这里&#xff0c;我们选用 Coding/Github/Gitee 等都可以作为我们的代码源 1 生成公钥私钥 首先&#xff0c;我们先来配置公钥和私钥。这是 Jenkins 访问 Git 私有库…

C#,计算几何,计算机图形学(Computer Graphics)洪水填充算法(Flood Fill Algorithm)与源代码

1 泛洪填充算法(Flood Fill Algorithm) 泛洪填充算法(Flood Fill Algorithm) &#xff0c;又称洪水填充算法&#xff0c;是在很多图形绘制软件中常用的填充算法&#xff0c;最熟悉不过就是 windows 自带画图软件的油漆桶功能。 2 源程序 using System; using System.Collecti…

10. Linux系统中wifi适配器找不到的解决方案

1. 说明 在linux系统中开启一个热点&#xff0c;一般有两种方式。一种使用create_ap在命令行中进行创建&#xff0c;另一种就是在系统自带的操作界面中手动开启。当手动开启热点时&#xff0c;有时会遇到wifi适配器找不到的问题&#xff0c;本博客记录一种可解决此问题的参考方…

高速稳定、网络隔离,解析“向日葵控控”远控方案在医疗行业应用

在医疗大健康领域&#xff0c;依托高速发展的信息化技术加速布局智能化&#xff0c;通过远程手段提高医疗服务质量、促进医疗资源共享、提升医疗工作效率&#xff0c;已成为医院和各类社区诊所等提供关键医疗服务部门近年来的发展目标之一。 同时&#xff0c;根据医疗领域的特殊…

测试开源C#人脸识别模块DlibDotNet

百度“C# 换脸”找到参考文献4&#xff0c;发现其中使用DlibDotNet检测并识别人脸&#xff08;之前主要用的是ViewFaceCore&#xff09;&#xff0c;DlibDotNet是Dlib的.net封装版本&#xff0c;后者为开源C工具包&#xff0c;支持机器学习算法、图像处理等算法以支撑各类高级应…

如何系统地自学 Python?

目录 Python 数据类型 控制结构 函数和模块 文件操作 异常处理 类和对象 列表推导式和生成器 匿名函数和高阶函数 面向对象编程 总结 Python Python是一种面向对象、解释型计算机程序设计语言&#xff0c;由Guido van Rossum于1989年发明&#xff0c;第一个公开发行…

SQL库操作

1、创建数据库 概念 创建数据库&#xff1a;根据项目需求创建一个存储数据的仓库 使用create database 数据库名字创建 数据库层面可以指定字符集:charset/character set 数据库层面可以指定校对集:collate 创建数据库会在磁盘指定存放处产生一个文件夹 创建语法 create …

深度学习基础(二)卷积神经网络(CNN)

之前的章节我们初步介绍了深度学习相关基础知识和训练神经网络&#xff1a; 深度学习基础&#xff08;一&#xff09;神经网络基本原理-CSDN博客文章浏览阅读924次&#xff0c;点赞13次&#xff0c;收藏19次。在如今的科技浪潮中&#xff0c;神经网络作为人工智能的核心技术之…

证件照(兼容H5,APP,小程序)

证件照由uniappuyui开发完成&#xff0c;并同时兼容H5、App、微信小程序、支付宝小程序&#xff0c;其他端暂未测试。 先看部分效果图吧具体可以下方复制链接体验demo 首页代码 <template><view class""><view class"uy-m-x-30 uy-m-b-20"…

DTV的LCN功能介绍

文章目录 LCN简介LCN获取LCN Conflict LCN简介 Logical Channel Number&#xff08;LCN&#xff09;是数字电视系统中用于标识和组织频道的逻辑编号。LCN的目的是为了方便用户浏览和选择频道&#xff0c;使得数字电视接收设备能够根据这些逻辑编号对频道进行排序和显示。 LCN…

vue如何动态加载显示本地图片资源

在实际开发中&#xff0c;根据某一个变量动态展示图片的情况有很多。实现方法分打包构建工具的差异而不同。 1、webpack的项目 require引入图片资源 2、vite的项目 new URL(url,base).href 疑问解答&#xff1a;为什么vite项目不可以用require&#xff1f; 原因在于&#xf…

如何对表格中的文字进行自动识别并录入?

随着人工智能技术的不断发展&#xff0c;越来越多的领域开始应用自动化技术来提高工作效率和减少人工干预。对于表格中的文字识别和录入&#xff0c;目前已经有一些技术可以实现自动化&#xff0c;下面是一些可能的方法&#xff1a; 一、图片类表格文字自动识别并录入解决方案…

国家治理的数据赋能及其秩序生产(五)

国家治理的数据赋能及其秩序生产(五) 文章目录 国家治理的数据赋能及其秩序生产(五)前言六、大数据赋能国家治理的场域文明(一) 数字国家(二) 数字政府(三) 数字社会七、大数据治理的期望前言 受数据垄断、数据壁垒和数据鸿沟的影响,国家治理会产生数据异化。因此,…

matplotlib绘图初步

文章目录 绘制曲线图完整流程图像属性 绘制曲线图 matplotlib是python中最常用的可视化库&#xff0c;提供了不同坐标系下的二十余种常用图像&#xff0c;并且提供了动态图像绘制的方法&#xff0c;可以满足科学计算中的绝大多数可视化需求。而在matplotlib中&#xff0c;绝大…

windows11本地深度学习环境搭建Anacond,keras,tensorflow,pytorch, jupyter notebook

前言 工欲善其事&#xff0c;必先利其器。 第一步 安装Anaconda 下载地址&#xff1a; https://www.anaconda.com/download 路径默认 这里都勾选上 然后会卡在这里&#xff0c;卡很久&#xff0c;不用管&#xff0c;等着就行 第二步 配置环境 conda env list 列出所有…

2、Web攻防-SQL注入-联合查询注入

用途&#xff1a;个人学习笔记&#xff0c;有所借鉴&#xff0c;欢迎指正&#xff01; 声明&#xff1a;只用于学习交流&#xff0c;点到为止&#xff0c;请勿非法测试。 概念&#xff1a; 联合查询注入&#xff1a;联合注入是回显注入的一种&#xff0c;也就是说联合注入的前…

pytest结合Allure生成测试报告

文章目录 1.Allure配置安装2.使用基本命令报告美化1.**前置条件**2.**用例步骤****3.标题和描述****4.用例优先级**3.进阶用法allure+parametrize参数化parametrize+idsparametrize+@allure.title()4.动态化参数5.环境信息**方式一****方式二**6.用例失败截图1.Allure配置安装 …

NFT Insider #120:福布斯在 The Sandbox 推出永久建筑,哈佛教授表示Web3 和 NFT 将会继续存在

引言&#xff1a;NFT Insider由NFT收藏组织WHALE Members &#xff08;https://twitter.com/WHALEMembers&#xff09;、BeepCrypto &#xff08;https://twitter.com/beep_crypto&#xff09;联合出品&#xff0c;浓缩每周NFT新闻&#xff0c;为大家带来关于NFT最全面、最新鲜…

2024年数学建模美赛详细总结以及经验分享

前言&#xff1a; 本文记录与二零二四年二月六日&#xff0c;正好今天是数学建模结束&#xff0c;打算写篇文章记录一下整个过程&#xff0c;以及一些感受、还有经验分享。记录这个过程的原因就是我在赛前&#xff0c;在博客上找了很久&#xff0c;也没有像我这么类似记…

联想开天昭阳N4620Z笔记本如何恢复出厂麒麟操作系统(图解)

联想开天昭阳N4620Z笔记本简单参数&#xff1a; 中央处理器&#xff1a;KX-6640MA G2 内存&#xff1a;8GB 固态硬盘&#xff1a;512GB SSD 显示器&#xff1a;14.0”FHD 电池&#xff1a;4Cell 操作系统&#xff1a;麒麟KOS中文RTM&#xff08;试用版&#xff09; 此款笔…