C语言进阶---动态内存管理

动态内存管理

  • 前言:
  • 一、为什么存在动态内存分配?
  • 二、动态内存函数的介绍
    • 1.数据在不同区域的储存:
    • 2、malloc和free
    • 3、calloc
    • 4、realloc
  • 三、常见的动态内存错误
    • 1、对NULL指针的解引用操作
    • 2、对动态开辟空间的越界访问
    • 3、对非动态内存开辟的空间进行free
    • 4、对动态内存开辟的空间只进行一部分释放
    • 5、对同一块动态内存多次释放
    • 6、动态开辟内存忘记释放(内存泄漏)
  • 四、几个经典的笔试题
  • 五、柔性数组
    • 1.什么是柔性数组?
    • 2、柔性数组的特点
    • 3、柔性数组的使用
    • 4、柔性数组的优势

前言:

一、为什么存在动态内存分配?

常规的情况:

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>
int main()
{int val = 20;//在栈空间上开辟4个字节char arr[10] = { 0 };//在栈空间上开辟10个连续空间的字节return 0;
}

在这里插入图片描述

二、动态内存函数的介绍

1.数据在不同区域的储存:

在这里插入图片描述

2、malloc和free

malloc函数的介绍:
malloc是C语言中开辟指定空间字节大小的函数:
在这里插入图片描述
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

free函数的介绍:
语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
在这里插入图片描述
free函数用来释放动态开辟的内存:

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。
#include <stdio.h>
int main()
{//代码1int num = 0;scanf("%d", &num);int arr[num] = { 0 };//代码2int* ptr = NULL;ptr = (int*)malloc(num * sizeof(int));if (NULL != ptr)//判断ptr指针是否为空{int i = 0;for (i = 0; i < num; i++){*(ptr + i) = 0;}}free(ptr);//释放ptr所指向的动态内存ptr = NULL;//是否有必要?return 0;
}

3、calloc

calloc也是动态内存开辟的函数:

  • 它是开辟num个size大小的字节空间
  • 与malloc不同的是,他在返回开辟空间的起始地址之前,已经把开辟的那个空间全部都初始化为0
int main()
{	int* p = (int*)calloc(10, sizeof(int));if (NULL != p){//使用空间}free(p);p = NULL;return 0;
}

calloc函数与malloc函数的最大区别是:calloc函数会将开辟的那块空间全部都初始化为0。

4、realloc

  • realloc函数的出现会让动态内存管理更加灵活!
  • 有时我们发现申请的空间要么过大,要么过小,不能够很好地充分利用开辟的内存空间,所以说这个时候realloc的出现如同救世主一般。
    在这里插入图片描述
    参数1(void* ptr):指向先前分配有内存块空间的指针,也可以是空指针,但是NULL的话,这个realloc函数的功能就和malloc一样。
    参数2(size_t size):内存块的新大小,以字节为单位,size_t就是无符号整形。

对于realloc的返回值有三种情况:

  • 情况1. 如果需要在原空间上再开辟新的空间,而且原空间后方有足够大的空间,够开辟,那么就在原空间后面追加:
    在这里插入图片描述

  • 情况2:如果原空间后方的空间已经被占用,没有地方再开辟。那么realloc函数会在堆区中重新找一块能够储存这么大空间的地方,并且返回这个新空间的地址,然后把原来空间的内存释放(还给操作系统)。
    在这里插入图片描述

  • 情况三:开辟空间失败返回NULL
    具体代码进行分析:

int main()
{int* p = (int*)malloc(100);if (p == NULL)//开辟失败{perror("malloc fail");exit(-1);}else{//开辟成功}p = realloc(p, 1000);//这样可行吗?//nono!假设realloc开辟失败返回NULL,相当于我们现在直接将NULL赋给了p,//那么原来的空间已经消失不在了,出现了内存泄漏。//解决方法:int* ptr = realloc(p, 1000);//空瓶思想:我们先创建一个指针变量相当于空瓶,来暂时存放新开辟的空间地址//如果开辟成功,我们再把它赋给原来空间的起始地址p。//如果开辟失败,那么这样也避免了内存泄漏,还会找到原来那块空间的地址:pif (ptr!=NULL){p = ptr;}else{perror("realloc fail");}free(ptr);ptr = NULL;return 0;
}

三、常见的动态内存错误

1、对NULL指针的解引用操作

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

不能对空指针进行free!!!

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);//不能对在栈区上开辟的内存进行free!!!
}

4、对动态内存开辟的空间只进行一部分释放

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

注意:忘记释放不再使用的动态开辟的空间会造成内存泄漏。 切记: 动态开辟的空间一定要释放,并且正确释放 。

四、几个经典的笔试题

例1:

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

请问:运行Test函数之后会出现什么结果?
程序直接崩溃了!?
答案:核心问题就是子函数中的p是临时变量,改变它并不会改变主函数中的str。
在这里插入图片描述
例2:

局部变量的指针不能做返回值,因为函数内的空间在函数返回后就会释放掉

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

答案:GetMemory函数返回的地址无法正常使用
解析:此题考的是:局部变量的指针不能做返回值,因为函数内的空间在函数返回后就会释放掉这一点。

例3:
运行结果?


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

答案:打印hello

例4:
运行结果?

void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);// Str那块地址虽然被释放掉了,还给了操作系统,但是它并不是空指针。if (str != NULL){strcpy(str, "world");printf(str);}//所以最终的结果会打印world
}int main()
{Test();
}

答案:打印world

五、柔性数组

1.什么是柔性数组?

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构体中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

例如:


struct Stu
{int i;int arr[] ;//柔性数组成员
};

2、柔性数组的特点

柔性数组的特点:

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

sizeof 返回的这种结构大小不包括柔性数组的内存:
举例:

typedef struct Stu
{int i;int arr[] ;//柔性数组成员
}Stu;int main()
{printf("%d\n",sizeof(Stu));return 0;
}

在这里插入图片描述

3、柔性数组的使用

int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{p->a[i] = i;
}
free(p);

4、柔性数组的优势

typedef struct st_type
{int i;int *p_a;
}type_a;
type_a *p = malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

在这里插入图片描述
----------------------------------------------------------------------------------------------

好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!
在这里插入图片描述

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

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

相关文章

C# 集合

C# 集合 集合集合接口和类型列表队列栈链表有序表字典LoopupHashSet位数组 集合 数组的大小是固定的。如果元素个数是动态的&#xff0c;就应使用集合类。List 和 ArrayList 是与数组相当的集合类。还有其他类型的集合&#xff1a;队列、栈、链表和字典。 集合接口和类型 集…

无线振弦采集仪在岩土工程安全监测中优化成本支出

无线振弦采集仪在岩土工程安全监测中优化成本支出 随着城市的快速发展以及建筑业的不断壮大&#xff0c;岩土工程的安全监测变得越来越重要。在岩土工程中&#xff0c;振弦是一种重要的监测手段&#xff0c;可以有效地评估土体的力学性质和变形情况。因此&#xff0c;无线振弦…

一文了解VR全景在城市园区和电子楼书的应用

引言&#xff1a; 虚拟现实&#xff08;VR&#xff09;技术在日常生活中越发普及&#xff0c;已经成为众多行业的宣传工具&#xff0c;房地产行业近些年来热度较低&#xff0c;VR全景为房地产展示带来了新方式&#xff0c;为购房者提供更真实、更直观的体验。 一&#xff0e;…

康耐视visionpro脚本CogRectangleAffine ,CogPolygon图形限定框,边界显示(划痕缺陷案例分享)

目录 1.划痕缺陷整体方案设计:2.测试一效果图:3.测试一脚本编写​:4.测试二效果图:5.测试二脚本编写:6.测试三效果图:7.​测试三脚本编写:测试版本:康耐视visionpro9.0 1.划痕缺陷整体方案设计: 2.测试一效果图: 3.测试一脚本编写​: CogRectangleAffine Rectangle…

Spring Boot 如何使用Liquibase 进行数据库迁移

在现代的应用程序开发中&#xff0c;数据库迁移是一个不可或缺的环节。它使开发人员能够有效地管理数据库模式的变化&#xff0c;确保应用程序与数据库之间的一致性。Liquibase 是一个流行的开源工具&#xff0c;用于管理数据库的版本控制和迁移。本文将介绍如何在Spring Boot应…

网络安全攻防:软件逆向之反汇编

网络安全是当今社会中一个非常重要的问题&#xff0c;而软件逆向工程是网络安全攻防中常用的一种技术手段。在软件逆向工程中&#xff0c;反汇编是一种基础而重要的技术。通过反汇编&#xff0c;我们可以将二进制程序转换为汇编语言&#xff0c;从而更好地理解程序的执行流程和…

go语言 rune 类型

ASCII 码只需要 7 bit 就能完整地表示&#xff0c;但只能表示英文字母在内的 128 个字符&#xff0c;为了表示世界上大部分的文字系统&#xff0c;发明了 Unicode &#xff0c;它是 ASCII 的超集&#xff0c;包含世界上书写系统中存在的所有字符&#xff0c;并且为每个代码分配…

mysql事务测试

mysql的事务处理主要有两种方法1、用begin,rollback,commit来实现 begin; -- 开始一个事务 rollback; -- 事务回滚 commit; -- 事务提交 2、直接用set来改变mysql的自动提交模式 mysql默认是自动提交的&#xff0c;也就是你提交一个sql&#xff0c;它就直接执行&#xff01;我…

react import爆红

如上所示&#xff0c;会标红&#xff0c; 解决办法&#xff1a;在vscode内部SHiftCtrlP 输入Reload window, 如上的第一个&#xff0c;选中后回车&#xff0c;标红就没了&#xff0c;非常好用。

为您的视频编辑应用添加动力,美摄视频剪辑SDK

在当今的数字化时代&#xff0c;视频已经成为了最受欢迎的媒体形式之一。无论是社交媒体平台&#xff0c;还是在线教学站点&#xff0c;甚至是商业广告&#xff0c;都离不开视频的支持。而在这个领域&#xff0c;美摄视频剪辑SDK无疑是您的最佳选择。它不仅功能强大&#xff0c…

【2023集创赛】芯原杯一等奖作品:基于芯原DSP核的智能语音SoC设计

本文为2023年第七届全国大学生集成电路创新创业大赛&#xff08;“集创赛”&#xff09;芯原杯一等奖作品分享&#xff0c;参加极术社区的【有奖征集】分享你的2023集创赛作品&#xff0c;秀出作品风采&#xff0c;分享2023集创赛作品扩大影响力&#xff0c;更有丰富电子礼品等…

C++之va_start、vasprintf、va_end应用总结(二百二十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

MASA MAUI iOS 文件下载与断点续传

文章目录 背景介绍方案及代码1、新建MAUI项目2、建立NSUrlSession会话连接3、使用NSUrlSessionDownloadTask 创建下载任务4、DidWriteData 监听下载5、DidFinishDownloading 完成下载6、CancelDownload (取消/暂停)下载7、ResumeDownload 恢复下载8、杀死进程-恢复下载 效果图总…

登录业务实现 - token登录鉴权

登录业务实现&#xff1a; 登录成功/失败实现 -> pinia管理用户数据及数据持久化 -> 不同登录状态的模板适配 -> 请求拦截器携带token&#xff08;登录鉴权&#xff09; -> 退出登录实现 -> token失效&#xff08;401响应拦截&#xff09; 1. 登录成…

Spring Boot 3.0:构建下一代Java应用的新方法

文章目录 Spring Boot 3.0&#xff1a;一览1. **模块化应用程序**2. **更强大的自动化工具**3. **更强大的安全性**4. **更好的性能和扩展性** 如何开始使用Spring Boot 3.01. 安装Spring Boot CLI2. 创建新项目3. 配置应用程序4. 编写业务逻辑5. 测试应用程序6. 构建和部署 结…

记录使用iText7查找PDF内容关键字坐标,加盖电子签名、印章

一、前言 项目以前签字都是由C端那边进行合成操作&#xff0c;最近项目要求把那块功能&#xff0c;由后端进行实现&#xff0c;其中包含坐标、关键字、任意位置进行签字操作&#xff0c;坐标是最容易实现的&#xff0c;曾经也写过类似的功能在&#xff08;添加图片印章到PDF&a…

印章篆刻小程序商城的作用是什么

印章的需求度也有很高市场需求&#xff0c;处理办公印章外&#xff0c;还有艺术类的&#xff0c;而对爱好者来说&#xff0c;需要找到一家靠谱的品牌制作&#xff0c;包括材料、样式、内容等都有较高要求&#xff0c;线上可以接触到更多雕刻商家。 而对品牌来说&#xff0c;需…

org.postgresql.util.PSQLException: Bad value for type long

项目用 springbootmybatis mybatisplus&#xff0c; 数据库是&#xff1a;postgresql 。 执行查询时候返回错误。 org.springframework.dao.DataIntegrityViolationException: Error attempting to get column city_id from result set. Cause: org.postgresql.util.PSQLExce…

华为云Stack的学习(七)

八、华为云Stack存储服务介绍 1.云硬盘EVS 云硬盘&#xff08;Elastic Volume Service&#xff0c;EVS&#xff09;&#xff0c;又名磁盘&#xff0c;是一种虚拟块存储服务&#xff0c;主要为ECS&#xff08;Elastic Cloud Server&#xff09;和BMS&#xff08;Bare Metal Se…

RabbitMQ消息可靠性保证机制--发送端确认

发送端确认机制 ​ RabbitMQ后来引入了一种轻量级的方式&#xff0c;叫发送方确认(publisher confirm)机制&#xff0c;生产者将信息设置成confirm&#xff08;确认&#xff09;模式&#xff0c;一旦信道进入了confirm模式&#xff0c;所有在该信道上面发送的消息都会被指派成…