栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

C语言动态内存分配(malloc(),free(),calloc(),realloc())

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言动态内存分配(malloc(),free(),calloc(),realloc())

目录

目录

1.介绍

2.动态内存使用的流程

2.1内存申请(malloc(),calloc())

2.2判断内存是否申请成功

2.3使用内存

2.4申请的内存不够大,申请扩容(realloc())

        扩容时的三种情况:

2.5释放内存(free())

       2.5.1free()函数方法的作用

       2.5.2 free()使用形式

       2.5.3 使用free()导致程序崩溃的原因

       2.5.4free(空指针)不会使程序崩溃

2.6内存指针指向NULL


1.介绍

        内存中分为代码段,数据区、堆区,和栈区。程序中的变量都在栈区存储,栈区只有1M(Windows)(10M Linux)大小,且由系统自己管理。而堆区根据实际情况不同也都有1.5G~1.9G大小,而且由程序员自己管理,所以我们可以自己通过stdlib.h头文件中的malloc()函数或calloc()函数自己在堆区开辟内存使用。所开辟的内存大小不能超过堆区的剩余空闲内存大小,否则程序会自己中止。

        申请得到的内存是在堆中由低地址向高地址开辟得到的,并且是连续的。

        在使用完内存后,必须进行内存释放,否则会使被开辟的内存一直被占用着,从而导致内存泄漏。

2.动态内存使用的流程

2.1内存申请(malloc(),calloc())

        1)malloc()

int n = 5;
int* p = (int*)malloc(n * sizeof(int));

        (1)malloc(size):size就是你想开辟的内存的字节大小。我们通常想要用这段内存存储n个某种类型的数据,所以如果我们想要用这段内存存储n个int大小(四字节大小)的数据(需要n*4字节内存),就使用malloc(n * sizeof(int))或malloc(n * 4)。

        (2)(int*):int就是你想要分割的单个格子大小。系统不知道这段内存是单个一段,还是你想将其分为几个单元格。所以我们需要给系统明确我们想要怎么样,如果我们想要用这段内存存储int大小的数据,就用int标记,系统自动识别,将这段内存分隔为4字节大小的连续单元格。还因为这是开辟的内存地址,需要用指针来指向所以用(int*)将其强转为指针。

        (3)int* p:对应想要存储的数据大小,就要选择对应指针解析存储单元大小相同,指针加整型能力相同的指针类型。malloc()将开辟好的内存起始地址返回回来,用指针进行保存。未成功开辟内存就返回一个空指针。

        2)malloc()在申请内存时不会对内存进行初始化赋值,在申请内存后,没有对内存进行初始化的话,这段内存中就存储着系统随机值。

 

        有两种给内存初始化的方式:

        (1)直接for循环给内存中每个单元格赋值。

for (int i = 0; i < n; i++) {
	p[i] = 1;
}

         (2)以string.h头文件中的memset()函数实现内存初始化。

memset(初始化内存(void*),int value,unsigned int size);
//初始化内存:所要初始化的内存地址,也就是指向该内存的指针
//value:想要以什么值进行初始化
//size:想要初始化的内存大小(字节大小)

        由于memset()函数是以字节为单位进行内存初始化的,所以memset(p,0,n*sizeof(int))可以将所开辟的内存的每个字节初始化为0。

         所以使用memset(p,1,n*sizeof(int))并不是将一个单元格中的数据初始化为1,而是将每个字节初始化为1。

memset(p,1,n*sizeof(int));

 

        此时,p[i]==16843009,将其转为二进制也就是0000 0001 0000 0001 0000 0001 0000 0001。

        3)上述两种初始化方法都需要在申请完内存后再初始化,多了一道步骤,比较麻烦。所以C语言还提供了calloc()函数,在初始化内存时,将分好的各个单元格初始化为对应数据类型的默认值。如:

//类型* 指针名 = (单元格解析大小 *)calloc(单元格数目,单元格大小);
int* p = (int*)calloc(n , sizeof(int));
double* p = (double*)calloc(n , sizeof(double));

        想要存储几个某种类型的数据,单元格数目就给几个,单元格大小也指定为对应数据类型大小。 

 

2.2判断内存是否申请成功

         如果内存申请成功,指针就指向了所申请的内存的起始地址。但如果没申请成功,malloc()或calloc()方法会返回NULL值,如p==NULL,这就是我们通常意义上的空指针。此时,指针就指向了内存中的0x0000 0000地址,这个地址不在堆区内,并且是不允许程序员访问的。如果不判断内存是否申请成功,就直接通过访问(使用)内存,就可能会因为内存未申请成功,导致指针指向0x0000 0000地址,再通过指针解引用访问这个禁止访问的内存,就会导致程序崩溃。

2.3使用内存

        在成功开辟好内存后,就可以对通过指向这段开辟好的内存的指针对内存进行访问,对内存中的数据进行操作处理。但是,要谨记,不能越界去访问这段内存外的其他内存。程序员只能访问自己开辟出的内存,否则会导致程序崩溃。

        并且,在使用内存时,不能使指向这段内存的指针偏移位置。因为这段内存如数组一般,指针是指向其起始地址的。在使用结束后,通过free(指针名)(即free(内存起始地址))达到释放整段内存的目的。如果在使用中,使指针指向了除了这段内存的起始地址之外的位置,必定会使在释放内存时,这段内存没有被全部释放,导致内存泄漏,或者释放了不该访问的内存,导致程序崩溃。所以,如果在使用时指针移动了位置,就必须让指针回到内存的起始位置。

2.4申请的内存不够大,申请扩容(realloc())

        realloc()使用格式:

//类型* 指针名 = (解析单元格大小*)realloc(要扩容的内存地址(指针名),扩容内存的目标总字节大小);
int* q = (int*)realloc(p,10*sizeof(int));

        扩容时的三种情况:

        在扩容时,会有3种情况发生(以原开辟内存大小为5个int型(20字节)大小,要扩容到10个int型(40字节)大小为例):

        1)堆区剩余空闲内存不足(不满足下面两个条件),开辟失败。

        2)堆区剩余空闲内存充足,并且在邻接着这段内存之后(高地址方向)有足够的空闲内存空间扩容。

 

        3)堆区剩余空闲内存充足,并且在邻接着这段内存之后(高地址方向)没有足够的空闲内存空间,在更高的内存地址有能放下扩容后内存大小的空闲内存空间扩容。

//类型* 指针名 = (解析单元格大小*)realloc(要扩容的内存地址(指针名),扩容内存的目标总字节大小);
//原先内存为5*4=20字节,现在扩容到10*4=40字节。
p = (int*)realloc(p, 10*sizeof(int));// 方法(1)  //error
if(p==NULL) exit(1);                
int *q = (int*)realloc(p, 10*sizeof(int));//方法(2) //right
if(q==NULL) exit(1);               

         1)因为reallloc()方法会因为堆区剩余空闲内存不足,导致扩容失败,返回NULL值,如果使用方法(1)直接用p保存realloc()函数方法的返回值,指针p就可能直接指向了0x0000 0000地址,然而原先开辟好的内存没有被释放,并且p指向了0x0000 0000地址,丢失了原内存的地址,从而导致该内存无法被释放,导致内存泄漏。所以需要使用方法(2)用一个新的指针q指向这段内存,再次进行判断内存是否扩容成功判断。再进行使用。

        2)如果在邻接着这段原来开辟好的内存之后(高地址方向)有足够的空闲内存空间,realloc()方法就会在原内存的基础上将内存向高地址方向扩容至目标大小,并且内存是连续的。

        但在实际情况中是没有2)这个前提条件的,所以还需要采用方法(2)。

        若开辟成功,p、q同时指向这段内存的起始地址。

        3)如果在邻接着这段内存之后(高地址方向)没有足够的空闲内存空间,在更高的内存地址有能放下扩容后内存大小的空闲内存空间。realloc()方法就会在这段内存空间新开辟一段扩容后内存大小的内存,将原内存中的数据拷贝过来。并且realloc()会自动将原内存空间释放掉。

        但在实际情况中也是没有3)这个前提条件的,所以还需要采用方法(2)。

        若开辟成功,p指向原内存的起始地址,q指向新的这段内存的起始地址。但原内存已经被释放,p指向的地址就是不能访问的无关地址,p就成了野指针,就要使p=NULL;若程序员还想用p来访问这个新内存,就用p=q,使将新地址赋值给p,使p指向新内存,在使用完内存后要记得使两个指针p、q都指向空,防止野指针产生。

2.5释放内存(free())

        在使用完内存后要及时将内存释放,防止影响其他部分程序使用。如果不主动释放自己开辟的内存,那这些内存就会一直被占用着,导致内存泄漏,直到程序运行结束才会将内存释放。如果是在服务器上,服务器基本上是一直在运行的,很长时间才会关闭,甚至一直在运行,如果只知道开辟内存而不释放,就会导致服务器内存被占完,导致服务器崩溃。这就会造成很严重的后果。所以及时释放使用过的内存是及其重要的。

       2.5.1free()函数方法的作用

        free(指针)的作用就是释放指针变量所指向的动态内存空间,使这部分空间能被其他变量重新使用。

       2.5.2 free()使用形式
//free(指向要释放的内存的起始地址地指针);
int n = 5;
int* p = (int*)malloc(n *sizeof(int));
free(p);//p存放的就是所开辟内存的起始地址
p = NULL;

       2.5.3 使用free()导致程序崩溃的原因

        (1)对野指针进行free():free()只允许对程序员自己开辟的内存进行释放,如果该内存已经被释放过了,则指向该内存的指针就变成了野指针。再对这个指针进行free()操作的话,就是对现在不属于程序员开辟的内存进行操作,所以会导致程序崩溃。

        (2)在使用开辟的内存时,对指向该内存的指针进行了移动:在使用中,使指针指向了除了这段内存的起始地址之外的位置,必定会使在释放内存时,这段内存没有被全部释放,导致内存泄漏,或者释放了不该访问的内存,导致程序崩溃。

        (3)对栈中变量进行free()操作:free()函数方法是专门针对释放堆区中程序员自己开辟的内存的。栈区有系统管理,程序员没有权限对栈区进行操作。

       2.5.4free(空指针)不会使程序崩溃

        在使用free()时会进行安全性检查,空指针(NULL)会被忽略,free什么也不做。

2.6内存指针指向NULL

        在释放完所开辟的内存后,指向该内存的指针就会变成野指针,所以要让其变成空指针。这也是为了防止对该内存进行多次释放。

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1036490.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号