「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://xiahunao.cn/news/2804328.html

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

相关文章

.netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项

1、SqlSugarCore 相关 1.1 主项目添加数据&#xff0c;否则会报数据库连接错误&#xff1a; <InvariantGlobalization>false</InvariantGlobalization> <PropertyGroup><TargetFramework>net8.0</TargetFramework><Nullable>enable</…

MES系统中的手动排产和自动排产-助力生产效率

企业在排产管理中面临的问题&#xff1a; 大多数的企业在调度排产过程中&#xff0c;都会遇到以下问题。首先是插单非常的多&#xff0c;计划调整困难&#xff0c;会经常性的发生原材料、零部件的备货不足。计划按MRP或库存展示计算出需求后将产生大量工单&#xff0c;这些工单…

代码随想录算法训练营day20||二叉树part07、● 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ● 236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差 【需要领悟一下二叉树遍历上双指针操作&#xff0c;优先掌握递归】 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 思路 题目中要求在二叉…

【Java】类与对象(实验二)

目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 掌握类的定义与对象的创建。理解构造方法和this关键字的用法。掌握对象对于属性及方法的引用。 二、实验内容 1、编写一个Java程序&#xff0c;定义一个表示学生的类Student&#xff0c;该类包括: (1)这个类的…

【SelectIO】bitslice原语学习记录

基本概念 在Ultrascale (plus)系列上的FPGA中&#xff0c;Xilinx引入了bitslice硬核&#xff0c;它取代了7系列上的IDELAYCTRL/IODELAY/IOSERDES/IODDR系列硬核&#xff0c;用于为HP&#xff08;High Performance&#xff09;类型Bank上的IO接口提供串并转化、信号延时、三态控…

读取7400MB/s!华为发布eKitStor Xtreme M.2闪存条

今日&#xff0c;华为举行数据存储新春新品发布会&#xff0c;不仅发布全新数据湖解决方案&#xff0c;华为还针对商业市场与分销市场发布了全闪存存储新品。 据介绍&#xff0c;面向游戏加速、影视编辑、户外作业等场景&#xff0c;华为发布eKitStor Xtreme系列高性能M.2闪存条…

静态时序分析:SDC约束命令set_driving_cell详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 在上文中&#xff0c;我们不建议使用set_drive命令而是使用set_driving_cell命令&#xff0c;这是一个描述输入端口驱动能力更精确的方法。因为大多数情况下&…

【Java网络编程06】HTTPS原理

1. HTTPS基本概念 HTTPS&#xff1a;HTTPS也是一个应用层协议&#xff0c;它在HTTP协议的基础上引入了一个加密层——SSL协议&#xff0c;区别就在于HTTP协议是基于明文传输的&#xff08;不安全&#xff09;&#xff0c;使用HTTPS加密就能在一定程度上防止数据在传输过程中被…

Keepalived实现Nginx的高可用集群案例

服务器规划: serverb(nginx2):192.168.233.144 serverc(客户端):192.168.233.140 serverd(nginx1):192.168.233.141 结构图: serverd(nginx1): # 安装nginx yum install nginx -y# 进入nginx配置目录 cd /e…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture07多维输入

lecture07多维输入 课程网址 Pytorch深度学习实践 部分课件内容&#xff1a; import torch import numpy as npxy np.loadtxt(diabetes.csv.gz, delimiter,, dtypenp.float32) x_data torch.from_numpy(xy[:,:-1]) #第一列开始最后一列不要 y_data torch.from_numpy(…

[已解决]npm淘宝镜像最新官方指引(2023.08.31)

最新的配置淘宝镜像的淘宝官方提供的方法 npm config set registry https://registry.npmmirror.com原来的 registry.npm.taobao.org 已替换为 registry.npmmirror.com &#xff0c;当点击 registry.npm.taobao.org 会默认跳转到 registry.npmmirror.com 如果你想将npm的下载…

python 打包 apk

转换之前python代码需要使用指定的框架才能转换&#xff0c;列如&#xff1a;kivy from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Buttonimport time import pyautogui import threadingstatus False# 这是一个将被线程执…

2022蓝帽杯取证初赛

检材&#xff1a;https://pan.baidu.com/s/1ibOdxyCWeC5x0DQKjwcz7w?pwdvg6g 目录 手机取证1、627604C2-C586-48C1-AA16-FF33C3022159.PNG图片的分辨率是&#xff1f;&#xff08;答案参考格式&#xff1a;19201080&#xff09;2、姜总的快递单号是多少&#xff1f;&#xff0…

项目开发Eclipse中Jar包的管理和使用

一、如何生成Jar包&#xff1f; Jar生成主要是给别人用的&#xff0c;可以通过Eclipse的Export功能&#xff0c;导出jar包文件。 1. 在Eclipse中&#xff0c;右键单击项目&#xff0c;并选择“Export”。 2. 在弹出的“Export”窗口中&#xff0c;选择“Java” -> “JAR f…

C# Onnx 使用onnxruntime部署实时视频帧插值

目录 介绍 效果 模型信息 项目 代码 下载 C# Onnx 使用onnxruntime部署实时视频帧插值 介绍 github地址&#xff1a;https://github.com/google-research/frame-interpolation FILM: Frame Interpolation for Large Motion, In ECCV 2022. The official Tensorflow 2…

《springcloud alibaba》 二 nacos配置中心

目录 父项目配置pom.xmlconfig-nacos项目pom.xml配置文件application.ymlbootstrap.yml 启动类配置 多环境配置groupRefreshScope 市面上配置中心大致分为三种 springcloud configapollonacos 推荐使用 父项目配置 pom.xml <?xml version"1.0" encoding"…

2024年阿里云服务器优惠价格表,值得买云主机清单

2024阿里云服务器优惠活动政策整理&#xff0c;轻量2核2G3M服务器61元一年、2核4G4M带宽165元1年&#xff0c;云服务器4核16G10M带宽26元1个月、149元半年&#xff0c;阿里云ECS云服务器2核2G3M新老用户均可99元一年续费不涨价&#xff0c;企业用户2核4G5M带宽199元一年&#x…

Netty是如何解决JDK中的Selector的bug的?

Selector BUG: JDK NIO的BUG, 例如臭名昭著的epoll bug&#xff0c;它会导致Selector空轮询&#xff0c;最终导致CPU 100%, 官方声称在JDK 1.6版本的update18修复了该问题&#xff0c;但是直到JDK1.7版本该问题仍旧存在&#xff0c;只不过该BUG发生 概率降低了一些而已&#x…

渗透测试—信息收集

渗透测试—信息收集 1. 收集域名信息1.1. 域名注册信息1.2. SEO信息收集1.3. 子域名收集1.3.1. 在线子域名收集1.3.2. 子域名收集工具 1.4. 域名备案信息1.5. ICP备案号查询1.6. SSL证书查询 2. 收集真实IP2.1. 超级ping2.2. Ping2.3. CDN绕过 3. 收集旁站或C段IP3.1. 旁站或C段…

桥梁防船撞监测预警系统解决方案

一、方案概述 随着航道交通的快速发展&#xff0c;通航船舶日益增多&#xff0c;船舶超高以及偏航带来诸多安全隐患&#xff0c;时常发生桥梁受到船舶碰撞的事故&#xff0c;轻则桥梁结构和船舶受到损伤&#xff0c;重则桥梁垮塌&#xff0c;不但经济损失巨大&#xff0c;更会造…