路漫漫其修远兮,吾将上下而求索。
回顾以往使用内存空间的时候,我们既可以直接创建一个变量来申请一小块内存空间,同样也可以申请一大块内存空间(即数组的创建),但是这两种向内存申请空间的形式存在两缺点:
联想到学习中写的通讯录,如果可以动态开辟空间的话,当已经开辟的空间不够的时候再开辟一块空间,不够用了再开辟……而不是一次性开辟一个足够大的空间来存放数据,这种方法不仅难以保证开辟的空间是否够用而且还极有可能浪费许多内存空间,无法做到“因地制宜”;故而动态内存分配的存在是合理的;
接下来我们便来了解一下动态内存;
malloc 的使用如下图,此处我使用malloc 的目的是想创建一块能存放10个整型的空间:
详细分析使用:
C语言中的习惯,当main函数 return 0 时以表示正常返回,而如若出现了问题便 return 1;
当你看到数组的所占用的内存空间不可修改时,是不是会想到C99标准中的变长数组?
敲重点!变长数组的意思并不是说这个数组的大小是可变的,而是说可以根据一个变量来确定一个数组的元素个数,而这个变量一旦确定下来后,数组元素的个数也就确定了;即变长数组可以动态输入一个数值来确定其数组元素的个数;
在上述代码中,我们利用malloc 向内存申请了空间,但是并没有free,便存在内存泄漏;
什么是内存泄漏?
既然没有归还那为什么内存的空间没有丢失?
如何释放内存空间呢?
C语言提供了一个专门用来释放和回收动态开辟内存函数:free;
将上面 举例的malloc代码完善:
注:当使用了动态开辟内存空间的函数时,不用的时候就一定要利用free 将此空间释放了;
使用calloc 动态开辟能存放10个整型的空间:
注: malloc 仅在堆区开启了一块空间;而calloc 在堆区上开辟空间的基础上还对此空间中的数据初始化为了0;
相当于 calloc 包含了 malloc 的功能;也可以这样理解 calloc = malloc + memset;
realloc(NULL, 40) ; //等价于 malloc(40);
你会不会有疑问,不是说动态开辟内存吗,但是上面的 malloc 和 calloc 也仅仅体现为开辟空间,”动态“体现在何处呢?请接着往下看,realloc 这个函数 与 malloc 和 calloc 的配合使用就能体现"动态内存开辟"喔~
在calloc 开辟了 40 byte 空间的基础上,再开辟40 byte 大小的空间:
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
//开辟空间
int* pf = (int*)calloc(10, 4);
if (pf == NULL)
{
perror("calloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(pf + i));
}
printf("\n");
//空间不够,我要再开辟 40byte 的空间,所以新空间的大小为 80 byte
int* ptr = (int*)realloc(pf, 80);
//realloc 也有可能会重新开辟空间失败
if (ptr == NULL)
{
perror("realloc");
return 1;
}
//再使用
pf = ptr;
memset(pf, 0, 80);
for (i = 0; i < 20; i++)
{
printf("%d ", *(pf + i));
}
//释放空间
free(pf);
pf = NULL;
return 0;
}
代码运行结果如下:
如若用指向上例中 calloc 动态开辟的空间的指针pf 来接收realloc的返回是非常危险的;
因为realloc 也有可能重新开辟空间而返回NULL,倘若realloc 返回NULL 并且用pf 来接收,那么便会找不到 calloc 开辟的空间(因为pf 指向calloc 所动态开辟的空间),就好比想让realloc 对旧空间进行“扩容”处理,没想到不仅realloc 扩容失败了,还将指向旧空间的指针pf 搞成了空指针,从而找不到此旧空间;
故而此处得创建一个新指针来接收 realloc 得返回值来避免“得不偿失”;
小技巧分享:在利用malloc、calloc 、realloc 开辟空间时其参数是所开辟空间的字节大小,直接写数字的字节数容易出错,那么就可以利用 sizeof(类型)*个数 的形式,例如 利用malloc 开辟能存放10个整型的空间便可以写作 int* pf = (int*) malloc (sizeof(int) * 10) ; 倘若想再利用realloc 扩容10个整型的空间 : int* ptr = (int* ) realloc ( pf , sizeof(int) * 20) ;
此小技巧会强行给自己增加一个思考的流程,强烈推荐;(PS:本人在注意力不太集中的情况下极易将上例中 realloc 再次开辟的代码写作 : int * ptr = (int *) realloc ( pf , 40) ; 实际上其第二个参数应该是 80);
分为两种情况,一是旧空间后面的空间够用以开辟新空间,二是旧空间后面的空间不够用;
情况一:
情况二:
所要重新开辟的空间后面放得下所要增加的空间,于是就直接在此空间的原位上进行重新开辟空间的操作;
分别调试来看此两种情况:
情况一:
情况二:
特别注意:如若频繁地使用 malloc、calloc、realloc 来开辟内存空间,便会使得内存空间的使用率下降以及效率下降;
在平时写代码中,倘若难以避免多次使用 malloc、calloc、realloc,而产生较多的内存碎片,那么针对较多的内存碎片,该怎么办呢?
在程序设计中,软件工程里有一个叫内存池的概念;
什么叫作内存池?(了解)
该程序首先会向内存申请一块相对来说可以满足当前程序所需的一块区域,而由于此块区域已分配给当前的程序,程序内部自己便会以内存池的方式来维护此空间;
当该程序想要使用一块空间,便会使用该内存池中的空间,不过用完之后便要将内存归还给内存池;正是程序自己在维护此块内存区域,于是将这块区域称为内存池;
内存池可以解决内存碎片,空间使用效率低下的问题;
简单来说,就是在利用 malloc、 calloc 、 realloc 函数动态开辟内存空间的时候没有去判断他们有没有开辟空间成功,而直接去使用这些函数的返回值;
错误代码如下:
修改后正确的代码如下:
只用向内存申请了的空间该程序才有使用该空间的权限,该程序去访问、使用不属于该程序的空间便就是非法访问;
同理,对动态开辟所空间进行越界访问便也是非法访问;
free 释放的空间是位于堆区上动态开辟的空间,即利用函数 malloc、calloc、realloc 所开辟的空间;倘若对非动态开辟的空间而利用free 释放是存在问题的;
(释放动态内存开辟的空间,没有完全释放)
例子如下:
故而用于接收 malloc 所开辟空间的返回值的指针 pf 在使用的过程中不可被修改,准确来说得保证有个指针指向这块动态开辟得空间,以确保free 可以次空间释放;
显然也是不可行得,因为利用free 将空间释放之后,为避免野指针得将指向这块空间得指针及时置空,但是倘若用 const 来修饰 指针pf 便无法执行操作 pf=NULL;
错误代码如下:
针对此错误有两种解决方案:
方案一:确保动态开辟的空间只利用 free 释放一次
方案二:在使用free 释放空间之后及时置空
方案一:
方案二:
因为NULL 为空指针,空指针代表着没有明确的指向空间,而free 是将某个动态开辟的空间所释放;free (NULL); free 的参数为 NULL,便就意味着free 不知道释放哪块空间--> 不用释放空间;故而不会产生太大的影响;
案例一:在函数中跳过了释放空间的代码
分析:在 test 函数中利用malloc 动态开辟的空间只能在 test 函数中被释放,因为用来接收malloc 返回值的指针pf是个局部变量,出了其作用域那么pf 便会被销毁即pf 所占用的内存空间会还给操作系统;故而倘若在test 函数之中未能将 pf 所指向的空间给释放了那么在后面便也没有可能能释放该空间;
案例二:使用封装的专门动态开辟内存空间的函数之后忘记释放空间
内存分为内核空间、栈、内存映射段、堆、数据段、代码段;
内存中的布局分布如下图所示:
注:看到这,你可能就会对之前的知识:当局部变量被static 修饰时,其生命周期会变长 理解更加深刻了;
在C99标准中,结构中的最后一个元素允许为未知大小的数组,这便叫做柔性数组的成员;柔性数组在结构体变量中有且只有一个;
写法:
注:这两种写法选其一便可,在一些编译器上倘若使用写法一会报错那便使用写法二;
具体分析:
结构体中柔性数组成员的前面必须至少有一个其他成员;因为柔性数组所占用的内存空间的大小是不确定的,故而sizeof 返回的这种包含柔性数组的结构的大小时不会计算柔性数组的大小;
显然,"柔性"体现在可曲可伸,那么其空间的开辟必然会利用到动态内存开辟的相关知识;
柔性数组作为结构体的成员,能否如同普通的结构体变量一样创建呢?
如下图所示:
显然这种写法是不可以的,因为sizeof(struct S) 的结果为4byte ,那么struct S s = {0};创建的结构体变量s 便只会向栈区申请 4byte 的内存空间,4 byte 大小的空间只能放下成员 i的数据,而没有给 数组a 任何的空间;故而此种写法不可行;
如何做?需要利用 malloc 进行动态内存的开辟;
代码如下:
#include<stdio.h>
#include<stdlib.h>
struct S
{
int i;
int a[];
};
int main()
{
//struct S s ={0 }; 这是错误的写法
struct S* pf = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 10);
if (pf == NULL)
{
perror("malloc");
return EXIT_FAILURE;
}
//使用
pf->i = 4;
int j = 0;
for (j = 0; j < 10; j++)
{
(pf->a)[j] = j;
}
for (j = 0; j < 10; j++)
{
printf("%d ", (pf->a)[j]);
}
//释放空间
free(pf);
pf = NULL;
return 0;
}
代码运行结果如下:
图解如下:
倘若利用malloc 开辟的这10 个整型的空间不够用,还可以利用realloc 对内存进行调整;
图解如下:
代码如下:
#include<stdio.h>
#include<stdlib.h>
struct S
{
int i;
int* a;
};
int main()
{
//为结构体开辟空间
struct S* ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
{
perror("struct S malloc");
return 1;
}
//为数组开辟空间
int* pf = (int*)malloc(sizeof(int) * 10);
if (pf == NULL)
{
perror("a malloc");
return 1;
}
//使用……
//释放
free(pf);
free(ps);
ps = NULL;
return 0;
}
这两种方式哪方式好?
利用柔性数组的好,即第一种方式好;存在两个原因:
故而,方法一优于方法二;
1、方便内存释放
2、有利于访问速度
1、malloc calloc realloc free 的使用
2、malloc calloc realloc 均有可能开辟空间失败,故而在使用之前要进行判空
3、释放动态开辟的空间之后置空,可以避免很多不必要的麻烦
4、内存分为内核空间、栈区、内存映射段、堆区、数据段、代码段
5、柔性数组存在于结构体中,并且在此结构体中有且只有一个柔性数组;在柔性数组成员之前至少有一个其他类型的成员;利用sizeof 计算该结构体的大小的时候,并不会计算柔性数组成员的大小;
6、柔性数组成员所占空间的大小需利用 malloc、calloc 来开辟,可以利用realloc 来调整其大小;
因篇幅问题不能全部显示,请点此查看更多更全内容