练习使用动态内存相关的4个函数:malloc、calloc、realloc、free

在了解使用动态内存相关的四个函数之前,我们先了解一下,为什么要有动态内存分配?

首先,我们已经掌握了一种开辟内存的方式,就是直接使用int i=20;但是这样开辟空间有两个特点,1:空间开辟大小是固定的,2:数组在创建时,必须设定数组的长度,数组空间的大小一旦确定就不能更改

可以申请和释放空间,这样就⽐较灵活了
 

malloc

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

如果开辟成功,返回一个开辟成功的指针。

如果失败,则返回NULL。因此,在malloc使用之前必须检查是否为空指针。

返回类型为void*,因此,返回的类型由自己来决定。同时,size为0的时候,malloc的行为是标准未定义的,这取决于编译器。

free

free函数是专门用来做动态内存的释放和回收的。

void free (void* ptr);

free函数用来做动态内存的释放,如果ptr指向的空间不是动态内存,那么free的行为就是标准未定义的。如果ptr指向的空间为空,那么free什么事都不做。

malloc和free都声明在 stdlib.h 头⽂件中。

传递给free的是要释放空间的起始地址,例如下面的案例,传递的不是起始地址,就无法释放:

2e4c62a2089448d2874d5c9649551601.png

在free之后一定要置为空,否则会造成一些意想不到的灾难。

calloc

calloc的初始化如下:void* calloc (size_t num, size_t size);
它为每个大小为size的num字节开辟空间,并初始化为0。其与malloc的区别是它会将所有字节初始化为0。

realloc

有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时
候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤
⼩的调整。

函数的原型如下:

void* realloc (void* ptr, size_t size);
ptr是要调整的内存地址,size为调整后的大小。返回值为调整后内存的起始位置。

这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
 情况1:原有空间之后有⾜够⼤的空间,空间续上,然后再返回起始空间的地址就可以了
情况2:原有空间之后没有⾜够⼤的空间

96b13926749b424588d2e1bda956e4c8.png

如果是情况一,增加空间直接追加在原有空间后面。 原有数据位置不发生变化,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。(满足新的大小要求)会将原来的数据拷贝一份到新的空间,释放旧的空间(realloc会主动把这块空间释放掉)。这样函数返回的是⼀个新的内存地址。

还有一种情况,调整失败,返回的是空指针。如下:

f412a254f6284ae68432e3621e7e9548.png

如果realloc调整失败了,空指针放到p里面,p原来还维护20个字节,现在20个字节释放了,也找不到了。

所以选择用新的指针ptr来接收新的空间地址

1324cfce108b403c9e43e028c4b61fe2.png

realloc函数可以完成和malloc一样的功能:

realloc(NULL,20);==malloc(20); 

下面举一个失败的案例:

3c269b8d92444735aca694189f1262a3.png

  not enough space

动态内存的常见错误:

1对NULL指针的解引⽤操作

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

原因:malloc的返回值需要判断

修改:

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

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

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


对⾮动态开辟内存使⽤free释放,是不能够运行的

使⽤free释放⼀块动态开辟内存的⼀部分

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

必须是起始位置,报错。

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

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

在释放完之后,及时把p置为空指针。

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

void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}

在test函数运行完成之后,就找不到malloc的一百个空间了。一旦出去,局部变量就销毁了。不用也不释放,就造成了内存泄漏的问题。

malloc,realloc,calloc申请的空间如果不想使用,都可以使用free来释放,当程序结束的时候,也会由操作系统释放。

尽量做到:谁申请的空间谁释放,如果不能释放,要告诉使用的人记得释放。

malloc和free成对出现。

但是架不住指针的空间可能提前释放,如下:

791e8269d9ff4a0dbfb3cf6838dea58e.jpg

动态内存经典笔试题分析

66bcfec1e89843e4b653b1cac703cdda.png

 832f758e0724429f959cc6bd30a62f87.jpg

 str为空指针。GetMemory本身是传值调用,p内也是NULL。malloc出空间的地址放到p指针中,p销毁,还给操作系统。当回来的时候,str依然为空指针。空指针没有指向有效的空间,

当结束最上面一个程序运行的时候,str的空间已经还给操作系统了,str已经是空指针了。出现对空指针的解引用操作,程序崩溃,不会打印。

那么如果对这个程序进行修改,使其正确呢?那么我们把str的地址传给GetMemory,char*是一级指针变量,那么要用二级指针接收

d63a82b8135b44ecb21c0cc5f63710c3.png

2b203b91af5f40d880898dde8d24201e.jpg

我们对修改后的程序,进行解释。

首先 创建一个指针,叫作str。str里储存一个空指针NULL。p指针里储存了一个地址,就是ptr的地址,指向ptr。而p malloc出100个空间的大小,所以相当于str指向这块空间。

f15c82b4a512457cbe26f3a8a6db1064.png

这个代码有什么问题呢? p所指向的空间还给操作系统。

上面这个代码,应该free(str);之后把str置为空。

eaccb9b522c048159a5199d06100b20e.png

如上图所示,在free之后置为空。 

柔性数组

柔性数组有哪些特点呢?

结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。

sizeof返回的这种结构⼤⼩不包括柔性数组的内存。

包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

柔性数组的使用:

#include <stdio.h>
#include <stdlib.h>
int main()
{
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);
return 0;
}

 用realloc让数组变大变小

柔性数组的优势:

#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
int i;
int *p_a;
}type_a;
int main()
{
type_a *p = (type_a *)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;
return 0;
}

上述 代码1 和 代码2 可以完成同样的功能,但是 ⽅法1 的实现有两个好处
第⼀个好处是:⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给用户。用户调⽤free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能依靠用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给用户⼀个结构体指针,用户做⼀次free就可以把所有的内存也给释放掉。
第⼆个好处是:这样有利于访问速度
连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。


 

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

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

相关文章

【业界动态】数字孪生到底意味着什么

什么是数字孪生&#xff1f;它可以理解为一种技术&#xff0c;也可以理解为某种生态。数字孪生即指将物理实体映射至虚拟空间&#xff0c;进而协助完成预测、决策等动作。随着互联网的建设与发展&#xff0c;数字孪生在未来又会如何落地&#xff1f; 一、数字孪生到底是什么&am…

Redis 教程系列之Redis 发布订阅(十五)

Redis 发布订阅 Redis 发布订阅(pub/sub)是一种消息通信模式&#xff1a;发送者(pub)发送消息&#xff0c;订阅者(sub)接收消息。 Redis 客户端可以订阅任意数量的频道。 下图展示了频道 channel1 &#xff0c; 以及订阅这个频道的三个客户端 —— client2 、 client5 和 cl…

【基于HTML5的网页设计及应用】——当前日期

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

红外遥控NEC协议

红外遥控技术在我们的日常生活中应用非常广泛&#xff0c;比如我们的遥控器。通过遥控器发射红外光&#xff0c;电视&#xff0c;空调装有红外接收管&#xff0c;负责接受红外光。那么本节将重点介绍其中的原理。 一、工作原理 上图的HS0038便是装在空调&#xff0c;电视上的红…

Redis中的事件(三)

时间事件 事件的调度与执行 因为服务器中同时存在文件事件和时间事件两种事件类型&#xff0c;所以服务器必须对这两种事件进行调度&#xff0c;决定何时应该处理文件事件&#xff0c;何时有应该处理时间事件&#xff0c;以及花多少事件来处理它们等等。事件的调度和执行由ae…

YOLOv9改进策略:卷积魔改 | PConv减少冗余计算和内存访问可以更有效地提取空间特征 |CVPR2023 FasterNet

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a;CVPR2023 提出的一种新的partial convolution&#xff08;PConv&#xff09;&#xff0c;通过同时减少冗余计算和内存访问可以更有效地提取空间特征&#xff0c;最后引入到YOLOv9。在detect前加入PConv&#x…

php反序列化——pop链构造[SWPUCTF 2021 新生赛]pop [NISACTF 2022]babyserialize

构造pop链 [SWPUCTF 2021 新生赛]pop 用反推法 从后往前推 这题的最后一步很明显 只要给$admin和$passwd赋值 就会echo flag 所以反推法第一步就是要调用Getflag()函数 找到$this->w00m->{$this->w22m}(); $this->mw00->{$this->w22m}();的意思是调用当…

Linux课程____shell脚本应用

:一、认识shell 常用解释器 Bash , ksh , csh 登陆后默认使用shell&#xff0c;一般为/bin/bash&#xff0c;不同的指令&#xff0c;运行的环境也不同 二、 编写简单脚本并使用 # vim /frist.sh //编写脚本文件&#xff0c;简单内容 #&#xff01;/bin/bash …

C++ :STL中vector扩容机制

vector是STL提供的动态数组&#xff0c;它会在内部空间不够用时动态的调整自身的大小&#xff0c;调整过程中会有大量的数据拷贝&#xff0c;为了减少数据拷贝的次数vector会在调整空间的时候尽量多申请一些空间&#xff0c;这些预留出的空间可以很大程度上减少拷贝的发生。 在…

WPF —— ContextMenu右键菜单 Canvas控件详解

ContextMenu右键菜单的实例 ​​​​​​​WPF中的右键菜单主要是通过ContextMenu来实现&#xff0c; 在控件中使用ContextMenu 直接在控件的ContextMenu属性中关联即可。 <Label Content"右键弹出内容菜单" FontSize"20" Width"200" Heig…

Redis面试题-缓存穿透,缓存击穿,缓存雪崩

1、穿透: 两边都不存在&#xff08;皇帝的新装&#xff09; &#xff08;黑名单&#xff09; &#xff08;布隆过滤器&#xff09; 解释&#xff1a;请求的数据既不在Redis中也不在数据库中&#xff0c;这时我们创建一个黑名单来存储该数据&#xff0c;下次再有类似的请求进来…

Go第三方框架--gin框架(二)

4. gin框架源码–Engine引擎和压缩前缀树的建立 讲了这么多 到标题4才开始介绍源码&#xff0c;主要原因还是想先在头脑中构建起 一个大体的框架 然后再填肉 这样不容易得脑血栓。标题四主要涉及标题2.3的步骤一 也就是 标题2.3中的 粗线框中的内容 4.1 Engine 引擎的建立 见…