【逐步剖C】-第十一章-动态内存管理

一、为什么要有动态内存管理

从我们平常的学习经历来看,所开辟的数组一般都为固定长度大小的数组;但从很多现实需求来看需要我们开辟一个长度“可变”的数组,即这个数组的大小不能在建立数组时就指定,需要根据某个变量作为标准。

如比较常见的就是一些编程题中,输入一个变量n来作为数组的长度等(PS:虽C99支持变长数组,但我们这里主要讨论数组共性的标准);可能还有一种情况就是在往数组中放数据时,由于一开始空间大小指定不合适,出现了空间不足的情况,此时就需要进行扩容”操作。

由此一来,动态内存管理应运而生。

二、动态内存函数

1、malloc和free

(1)malloc函数介绍

  • 函数的声明为
void* malloc (size_t size);
  • 函数的作用
    这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针,通过相应类型的指针变量接收返回的指针后,即可通过该指针变量使用已开辟的内存空间
  • 函数的特性
    如果开辟成功,则返回一个指向开辟好空间的指针。
    如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
    返回值的类型是void* ,因为malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
    如果参数 size为0,malloc的行为是标准是未定义的,取决于编译器

使用示例:

    int n = 0;scanf("%d", &n);int* a = (int*)malloc(sizeof(int) * n);//开辟大小为n个整型的空间if (a != NULL)	//判断空间是否开辟成功{a[0] = 1;	//对空间进行使用//...}

可以看到,我们在使用时需要通过强制类型转换“告诉”编译器我们开辟空间的类型(返回指针的类型)(PS:其实空间并没有所谓的“类型”的概念,仅是通过指针的不同类型而有不同的看待空间的视角

(2)free函数介绍

  • 函数声明为
void free (void* ptr);
  • 函数的作用
    释放动态开辟的内存
  • 函数的特性
    如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
    如果是动态开辟的,则该函数会将ptr指向空间的使用权进行归还,其中有两部分,大部分归还给操作系统(真正释放),另一部分未归还操作系统的已释放内存被恢复到空闲池(free pool)中,并可再次进行分配
    如果参数 ptrNULL指针,则函数什么事都不做。

使用示例:

    int n = 0;scanf("%d", &n);int* a = (int*)malloc(sizeof(int) * n);//开辟大小为n个整型的空间if (a != NULL)	//判断空间是否开辟成功{a[0] = 1;	//对空间进行使用//...}free(a);	//释放动态开辟的内存

使用注意事项
从函数的特性中我们可以看出,在free一块动态开辟的内存后,那块空间按理来说已经不能再访问了,虽然的确可以故意去访问,当访问到的是空闲池中的内存时可能不会有什么大问题,但若访问到了操作系统的内存程序就会崩溃;又因为free仅负责将动态开辟的空间的使用权进行归还,并不对用于接收这块空间起始地址的指针进行处理(示例中a再free之后仍指向那块空间),故为了内存访问的安全,在free之后需对相应的指针进行置空操作。(示例中最后还需加上a = NULL

2、calloc

(1) 函数的声明为

void* calloc (size_t num, size_t size);

(2)函数的作用
作用和malloc一样,唯一不同的是它可以将动态开辟好的空间进行初始化,即将开辟好的num个空间的值都初始化为0。
(3)函数的特性
同malloc,但多了一个初始化功能。

使用示例:

    int n = 0;scanf("%d", &n);int* a = (int*)calloc(n,sizeof(int));//开辟大小为n个整型的空间

3、realloc

(1) 函数的声明为:

void* realloc (void* ptr, size_t size);

(2)函数的作用
对动态开辟内存大小进行调整,一般用于给已开辟的空间进行扩容。

(3)函数的特性
参数ptr是要调整的内存地址,size是调整之后的新大小(一般即为新空间的大小为原空间大小+需要扩展的空间的大小)。

若参数size为0或空间开辟失败时,函数返回NULL

若参数ptrNULL,该函数等价于malloc,如:

int*p = NULL;
p = realloc(ptr, 1000);

空间开辟成功会分为两种情况

  • 原开辟空间后有足够空间可以扩容,那么函数直接在原有内存之后直接追加空间,原来空间的数据不发生变化,返回旧的起始地址(ptr的值)

  • 在这里插入图片描述

  • 原开辟空间空间无法满足扩容需求,函数会先在堆空间上另找一个新的合适大小的连续空间来使用,接着把原空间的数据拷贝至新空间前面的位置,并把原空间释放,最后返回一个新空间的内存地址
    在这里插入图片描述

从特性中我们得到使用时的重要一点:
我们在对原空间进行扩容时,需用一个同类型的新指针来接收扩容之后返回的指针,以防分配失败而造成原有数据的丢失,如:

int *ptr = (int*)malloc(100);
ptr = (int*)realloc(ptr, 1000);//如上代码中,若realloc分配失败放回空指针,ptr所指向的原空间数据丢失
//正确应写为:int *ptr = (int*)malloc(100);
int *tmp = (int*)realloc(ptr, 1000);
if(tmp != NULL)
{ptr = tmp;
}

补充一点
realloc函数一般仅用于对内存进行扩展,也就是扩容;很少会用于缩容,并且缩容会存在一些问题,并且可能不能达到我们预期的效果。如下通过VS2022下的调试说明一下:
用于调试的代码:

int main()
{int* p = (int*)malloc(10 * sizeof(int));p[5] = 1;p = (int*)realloc(p, 5*sizeof(int));p[5] = 2;return 0;
}

在这里插入图片描述
分配10个整型的空间后,将第6个整型空间的值改为1,没问题;接下来将p的空间缩减为5个整型空间:
在这里插入图片描述
可以看到,后5个整型空间好像确实是回收给系统了,那么接下来执行a[5] = 2;应为越界访问的行为,系统按理来说会报错,但实际是:
在这里插入图片描述
仍完成了对原空间第6个整型空间的值的更改,这就与我们的预期效果大相径庭了。故一般不用realloc来进行缩容。

三、常见动态内存错误

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

若对动态开辟返回的指针不做检查就可能发生,如:

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

2、对动态开辟的内存空间进行了越界访问

和数组越界访问的问题类型:

int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{exit(-1);
}
for(i=0; i<=10; i++)
{*(p+i) = i;		//当i是10的时候越界访问
}
free(p);

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

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

如上代码运行后系统崩溃:
在这里插入图片描述

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

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

如上代码运行后系统崩溃:
在这里插入图片描述

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

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

如上代码运行后系统崩溃:
在这里插入图片描述

6、使用完动态开辟的内存后忘记释放

若使用完动态开辟的空间后没有通过free函数进行内存释放,就会造成恐怖的内存泄露问题,体现在我们的程序中可能没什么大问题(程序会结束或关闭,顺带着内存就会回收);但若体现在一些长时间不停机服务器中,就会造成服务器越用越卡直至死机的严重后果。

四、关于内存管理的经典题目

了解完动态内存管理的基础知识后,可以看看一些关于内存管理的经典题目来趁热打铁,请看:
1、运行Test 函数的结果是

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

结果
程序会崩溃。
原因
解引用了空指针。在GetMemory函数中动态分配内存后返回的指针赋值给了p,但由于形参对实参的临时拷贝,故改变了p并不影响str,故在GetMemory函数调用结束后,str的值仍为NULL,在进行strcpy时发生了错误。

2、运行Test 函数的结果是

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

结果
程序会打印“烫烫烫烫烫烫…”的乱码
原因
返回栈空间地址问题。GetMemory中p为局部变量,其在出了GetMemory函数作用域后会销毁,销毁后其原指向的空间的值就为随机值,也就是说用str接收的指针所指向的空间随机值,故再以打印字符串的方式去打印str的内容时就会乱码。

3、运行Test 函数的结果是

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

结果
正常输出hello
原因
其实这才是相对于题目1的正确写法,由于实参本身就是一个指针,故需要一个二级指针来实现改变形参而改变实参

4、运行Test 函数的结果是

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

结果
看似正常输出了world
原因
在介绍free函数时有说到,free仅将动态开辟的空间的使用权还给系统,但并不改变用于“接收”(指向)这块空间的指针变量的值。也就是说,代码中的str在free后的值仍未原空间的起始地址,不为空,但此时对原空间已没有使用权,故在进行strcpy时本质上已经是非法访问了内存空间,只是可能访问到的是空闲池而程序没有崩溃。

五、C/C++内存区域划分

C/C++内存区域主要划分为如下几个区域:
1、栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2、 堆区(heap):一般由程序员申请分配与释放, 若程序员不释放,程序结束时可能由操作系统回收 。

3、数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。

4、代码段:存放函数体(类成员函数和全局函数)的二进制代码

六、拓展:柔性数组

1、定义

(1)概念:C99 中,结构中的最后一个元素允许是未知大小的数组,该数组就称为柔性数组成员
(2)定义方式

typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;

typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;

2、柔性数组的特点

其实包含柔性数组成员的结构体的特点,主要有如下三点:
(1)结构中的柔性数组成员前面必须至少一个其他成员
(2)sizeof 返回的这种结构大小不包括柔性数组的内存
(3)包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

3、柔性数组的使用及优势

(1)柔性数组的使用
柔性数组的使用主要在于对需要开辟的空间大小的把握,空间正确开辟后,和正常作为结构体成员的数组一样使用即可。下面是使用示例,请看:

type_a *p = (type_a*)malloc(sizeof(type_a)+10*sizeof(int));
//这里的柔性数组成员相当于获得了10个连续的整型空间//和正常数组一样使用即可
for(int i=0; i<10; i++)
{p->a[i] = i;
}free(p);

(2)与指针成员相比的优势
如上的结构体type_a 也可设计为:

typedef struct st_type
{int i;int* p_a;
}type_a;

这样在分配和释放空间时就会相对麻烦一些:

type_a *p = (type_a *)malloc(sizeof(type_a));
//先为整个结构体分配空间p->p_a = (int *)malloc(p->10 *sizeof(int));
//才能为里面的指针成员p分配空间for(int i=0; i<10; i++)
{p->p_a[i] = i;
}//要先释放指针成员的空间
free(p->p_a);
p->p_a = NULL;
//再释放整个结构体的空间
free(p);
p = NULL;

相比之下,我们可以得到柔性数组成员的两个优势:
(1)方便内存释放
有柔性数组成员的结构体在释放空间时仅需将为整个结构体分配的内存释放即可;而有指针成员的结构体在释放空间时需先释放指针为指针成员开辟的空间,才能释放为整个结构体开的空间,否则就会造成内存泄漏。
此时若是我们自己写的代码可能知道要先释放结构体指针成员的空间,但如果写的代码是给别人用时,用户可能就想着释放结构体就行,而不再会去在意结构体里有什么。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉,降低了内存泄漏的风险。
(2)一定程度上提高了内存访问速度
柔性数组成员的地址空间相对于整个结构体成员的空间地址是连续的,而指针成员则是碎片化的;如下示意图:

  • 柔性数组成员
    在这里插入图片描述
  • 指针成员

在这里插入图片描述

连续的内存有益于提高访问速度。

本章完。

看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或看不懂的地方或有可优化的部分还恳请朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹

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

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

相关文章

创建vue3工程

一、新建工程目录E:\vue\projectCode\npm-demo用Visual Studio Code 打开目录 二、点击新建文件夹按钮&#xff0c;新建vue3-01-core文件夹 三、右键vue3-01-core文件夹点击在集成终端中打开 四、初始化项目&#xff0c;输入npm init 一直敲回车直到创建成功如下图 npm init 五…

MATLAB 函数签名器

文章目录 MATLAB 函数签名器注释规范模板参数类型 kind数据格式 type选项的支持 使用可执行程序封装为m函数程序输出 编译待办事项推荐阅读附录 MATLAB 函数签名器 MATLAB 函数签名器 (FUNCSIGN) &#xff0c;在规范注释格式的基础上为函数文件或类文件自动生成函数签名&#…

select完成服务器并发

服务器 #include <myhead.h>#define PORT 4399 //端口号 #define IP "192.168.0.191"//IP地址//键盘输入事件 int keybord_events(fd_set readfds); //客户端交互事件 int cliRcvSnd_events(int , struct sockaddr_in*, fd_set *, int *); //客户端连接事件 …

国庆day5

客户端 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);socket new QTcpSocket(this);//此时&#xff0c;已经向服务器发送连接请求了&#xff0c;如果成功连…

第一百六十四回 如何实现NumberPicker

文章目录 1.概念介绍2.使用方法2.1 NumberPicker2.2 CupertinoPicker 3.示例代码4.内容总结 我们在上一章回中介绍了"如何在任意位置显示PopupMenu"相关的内容&#xff0c;本章回中将介绍如何实现NumberPicker.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1.概…

【重拾C语言】四、循环程序设计(后判断条件循环、先判断条件循环、多重循环;典例:计算平均成绩、打印素数、百钱百鸡问题)

目录 前言 四、循环程序设计 4.1 计算平均成绩——循环程序 4.1.1 后判断条件的循环 a. 语法 b. 典例 4.1.2 先判断条件的循环 a. 语法 b. 典例 4.1.3 for语句 a. 语法 b. 典例 4.2 计算全班每人平均成绩—多重循环 4.2.1 打印100以内素数 4.2.2 百钱百…

System Generator学习——使用 AXI 接口和 IP 集成器

文章目录 前言一、目标二、步骤1、检查 AXI 接口2、使用 System Generator IP 创建一个 Vivado 项目3、创建 IP 集成设计&#xff08;IPI&#xff09;4、实现设计 总结 前言 在本节中&#xff0c;将学习如何使用 System Generator 实现 AXI 接口。将以 IP 目录格式保存设计&am…

【MATLAB-基于直方图优化的图像去雾技术】

【MATLAB-基于直方图优化的图像去雾技术】 1 直方图均衡2 程序实现3 局部直方图处理 1 直方图均衡 直方图是图像的一种统计表达形式。对于一幅灰度图像来说&#xff0c;其灰度统计直方图可以反映该图像中不同灰度级出现的统计情况。一般而言&#xff0c;图像的视觉效果和其直方…

HUAWEI悦盒ec6108v9c 如何刷成海纳思系统(家用低功耗服务器,使用Home Assistant服务)

环境&#xff1a; 1.HW悦盒ec6108v9c一套 2.16G U盘 3.格式化软件USB_format.exe 4.固件 mv100-mdmo1g-usb-flash.zip&#xff08;底层是Ubuntu 20.04系统&#xff09; 5.十字螺丝刀 6.翘片/薄铲子 7.有线网络环境 8.镊子/回形针 问题描述&#xff1a; 最近玩智能家居…

OpenCV项目开发实战--使用最先进的方法“F、B、Alpha Matting”进行图像抠图--提供完整代码

示范 让我们对现实生活中的图像启动 FBA Matting 方法。要应用 FBA Matting 算法,我们首先需要生成一个 trimap(我们稍后会介绍它是什么)。在我们的演示中,我们将使用预训练的DeepLabV3生成分割掩模,其中每个像素属于前景类的概率。之后,我们将使用大量膨胀操作将边界像…

面向无线传感器网络WSN的增强型MODLEACH设计与仿真(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

QT4.8.7安装详细教程

QT4.8.7安装详细教程&#xff08;MinGW 4.8.2和QTCreator4.2.0&#xff09; 1.下载及安装2.配置环境 此文是在下方链接博文的基础上&#xff0c;按自己的理解整理的https://blog.csdn.net/xiaowanzi199009/article/details/104119265 1.下载及安装 这三个文件&#xff0c;顺序是…

微信小程序代驾系统源码(含未编译前端,二开无忧) v2.5

简介&#xff1a; 如今有越来越多的人在网上做代驾&#xff0c;打造一个代驾平台&#xff0c;既可以让司机增加一笔额外的收入&#xff0c;也解决了车主酒后不能开发的问题&#xff0c;代驾系统基于微信小程序开发的代驾系统支持一键下单叫代驾&#xff0c;支持代驾人员保证金…

Elasticsearch安装访问

Elasticsearch 是一个开源的、基于 Lucene 的分布式搜索和分析引擎&#xff0c;设计用于云计算环境中&#xff0c;能够实现实时的、可扩展的搜索、分析和探索全文和结构化数据。它具有高度的可扩展性&#xff0c;可以在短时间内搜索和分析大量数据。 Elasticsearch 不仅仅是一个…

C++ 类和对象篇(四) 构造函数

目录 一、概念 1. 构造函数是什么&#xff1f; 2. 为什么C要引入构造函数&#xff1f; 3. 怎么用构造函数&#xff1f; 3.1 创建构造函数 3.2 调用构造函数 二、构造函数的特性 三、构造函数对成员变量初始化 0. 对构造函数和成员变量分类 1. 带参构造函数对成员变量初始化 2. …

MyBatisPlus(十一)判空查询:in

说明 判空查询&#xff0c;对应SQL语句中的 in 语句&#xff0c;查询参数包含在入参列表之内的数据。 in Testvoid inNonEmptyList() {// 非空列表&#xff0c;作为参数List<Integer> ages Stream.of(18, 20, 22).collect(Collectors.toList());in(ages);}Testvoid in…

Gorsonpy的计算器

Gorsonpy的计算器 0.页面及功能展示1. PSP表格2.解题思路描述3.设计实现过程4.程序性能改进5.异常处理6.单元测试展示7.心路历程和收获 这个作业属于哪个课程https://bbs.csdn.net/forums/ssynkqtd-05这个作业要求在哪里https://bbs.csdn.net/topics/617294583这个作业的目标完…

手边酒店V2独立版小程序 1.0.21 免授权+小程序前端

手边酒店小程序独立版酒店宾馆订房系统支持创建多个小程序&#xff0c;让每一个客户单独管理属于自己的小程序。后台支持一键入住&#xff0c;一键退款、退押金、钟点房支持微信支付、模板消息。客服实时收到新的订单信息&#xff0c;可以在手机端处理订单。支持按日期维护房价…

基于SpringBoot的智能推荐的卫生健康系统

目录 前言 一、技术栈 二、系统功能介绍 用户管理 科室类型管理 医生信息管理 健康论坛管理 我的发布 我的收藏 在线咨询 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在…

python+pygame+opencv+gpt实现虚拟数字人直播(一)

AI技术突飞猛进&#xff0c;不断的改变着人们的工作和生活。数字人直播作为新兴形式&#xff0c;必将成为未来趋势&#xff0c;具有巨大的、广阔的、惊人的市场前景。它将不断融合创新技术和跨界合作&#xff0c;提供更具个性化和多样化的互动体验&#xff0c;成为未来的一种趋…