irpas技术客

C语言进阶第七篇【动态存储和柔性数组】_@每天都要敲代码

irpas 5192

?前言:Hello! 我是@每天都要敲代码!今天带大家学习一下动态内存存储:malloc、free、calloc、realloc;这第一课内容很重要,特别是在数据结构中应用很广泛;我们一定要掌握!今天就让我们一步一个脚印,一块学习吧!

在这之前我们先补充一个知识点:

(1)栈区:局部变量,函数形参===》临时使用,不要开辟多大的空间! (2)堆区:动态内存开辟(malloc free realloc calloc)! (3)静态区(数据段):全局变量,静态变量!

动态内存存储:malloc、free、realloc、calloc引用的头文件都是<stdlib.h>

目录

1. malloc

??例:malloc使用

??静态开辟空间的方式

??动态开辟空间的方式

2. free

??例:free使用

3. calloc

??calloc和malloc函数的对比:

??不同点1:参数个数不同

??不同点2:有无初始化

4. realloc

??例:realloc使用

??第一步先calloc动态开辟

??第二步再realloc动态扩容

??例:realloc单独使用和malloc使用效果类似

5.?常见的动态内存错误

??5.1 对(空指针)NULL指针的解引用操作

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

??5.3?使用free释放非动态开辟的空间

??5.4?使用free释放动态内存的一部分

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

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

6. 几个经典的笔试题

??6.1??题目1:

??修改方法1:

??修改方法2:

??6.2? 题目2:

??6.3? 题目3:

??6.4? 题目4:

??6.5? 题目5:

??6.6? 经典例题补充

7.?C/C++程序的内存开辟

8. 柔型数组

8.1 柔性数组的特点

??例:

8.2 柔性数组的使用

??计算大小

??柔性数组的动态创建

??利用指针实现柔型数组的功能

9. 习题补充

??9.1?习题1

??9.2?习题2

??9.3?习题3


1. malloc

??void* malloc (size_t size);默认返回的是void*类型!

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

??例:malloc使用

??静态开辟空间的方式

(1) 空间开辟大小是固定的。 (2)数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

静态开辟的空间是在栈区上开辟的!?出了这个函数就会被销毁!

??动态开辟空间的方式

?对于动态开辟的空间,我们要注意以下几点:

(1)开辟空间是以字节为单位的,比如我们要开辟10个整形的大小,实际上就是开辟40个字节;

(2)malloc默认的返回值是void*指针;所以我们在使用时,根据类型来进行强制类型转化;比如这里就强制类型转化为整型指针int*;

(3)创建好以后,一定要进行判空处理;开辟成功就会返回一个指针;开辟失败就会返回一个空指针NULL,此时就需要退出程序;

(4)动态开辟成功后,我们就可以使用了!使用完以后要进行free释放;并手动置位空指针NULL;不然会造成内存泄漏。

动态开辟的空间是在堆区上开辟的;需要我们手动释放!

2. free

??void free (void* ptr)

??free函数用来释放动态开辟的内存。

? ? ?(1)如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的;简而言之就是:free只能释放动态创建的地址! ? ? ?(2)如果参数 ptr 是NULL指针,则函数什么事都不做。

??例:free使用

正确的使用方法就是释放动态开辟的空间;例如:

?我们动态创建一个空间用指针p来接收;进行:判空、使用过后;就需要释放销毁,主要语法就是:free(p);p = NULL

总结:malloc是开辟空间,free是开辟空间;所以malloc和free一般是成对存在!

3. calloc

??void *calloc( size_t num, size_t size )

??函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。 ??与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

??calloc和malloc函数的对比: ??不同点1:参数个数不同

(1)对于malloc开辟空间,我们的参数是一个:void* malloc (size_t size);比如开辟10个整型的大小:int* p = (int*)malloc( 10 * sizeof(int) );只有一个参数就是:10 * sizeof(int)!

(2)对于calloc开辟空间,我们的参数是一个:void *calloc( size_t num, size_t size );比如开辟10个整型的大小:int* p = (int*)calloc( 10 ,?sizeof(int) );有两个参数就是:10 和 sizeof(int)!

??不同点2:有无初始化

用malloc开辟的空间,默认是随机值!用calloc开辟的而空间,默认是初始为0!

(1)对于malloc

(2)对于calloc?

4. realloc

当我们用malloc或者calloc动态开辟空间后;怎样继续进行扩容呢?这就需要realloc!

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

??ptr是要调整的内存地址;size 调整之后新大小;返回值为调整之后的内存起始位置。 ??realloc在调整内存空间的是存在两种情况: ? ? ?(1)后面的空间足够,返回旧的地址。

? ? ?(2)后面的空间不够:1. 重新造内存创建一个新的内存空间;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2. 把原来的内容拷贝下来; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?3. 把原来的空间释放掉;?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?4. 把新空间的地址返回去; ? ?(3)最终的返回地址,不能用原来的指针进行接收;如果realloc有可能找不到合适的空间,调整大小;这是会返回NULL!

??例:realloc使用

??第一步先calloc动态开辟

我们先使用calloc进行第一次的动态开辟空间;经过判空处理、使用;发现空间不够想要继续开辟新的空间;此时就需要realloc函数!

??第二步再realloc动态扩容

(1)void* realloc (void* ptr, size_t size);第一个参数是旧地址,size是要再次开辟的空间大小,返回值同样是void*指针,需要我们强制类型转换!

(2)最重要的是,我们创建一个新的指针ptr去接收,不能用以前的旧指针p;因为有可能开辟失败,返回一个空指针,如果用旧的指针p接收就会使得p可能被置位空NULL!

(3)对新指针ptr进行判空处理,成功开辟,就把新的指针ptr在赋值给旧指针p;最终使用完在释放p:free(p),p = NULL就可以了!

??例:realloc单独使用和malloc使用效果类似

如果我们直接使用realloc函数,那么才开始起始地址为NULL;后面40就是开辟空间的大小!效果不就和malloc直接开辟40个字节的大小一样?所以这里的功能就类似于malloc,就是在堆区开辟40个字节!

5.?常见的动态内存错误 ??5.1 对(空指针)NULL指针的解引用操作

我们开辟一个很大的空间是有可能开辟失败的!在开辟后并没有进行判空处理,直接使用,这就有可能因为开辟空间失败(返回空指针NULL)造成对空指针的解应用操作!?

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

我们动态开辟空间是以字节为单位的,开辟的是40个字节,实际上就是10个整型;使用时用40个字节就会造成越界访问异常!?

??5.3?使用free释放非动态开辟的空间

我们静态创建的数组,实际上就是在栈区上创建的;不需要free来释放,栈区间里的变量除了这个函数就会被销毁!只有malloc等动态创建,在堆区间上创建的,才需要free!

??5.4?使用free释放动态内存的一部分

释放内存的一部分,什么意思呢?我们发现*p++说明指针一直往后走的 ;p指向的就不是起始地址,此时释放p就是释放p的一部分,并没有把p的地址完全释放掉!

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

这个问题一般会出现在写项目里,我们已经在函数里释放过了,结果出了函数又释放一次;这就造成了多次释放,也会使编译器出现问题!但是如果我们释放过后,立马进行置空处理(p = NULL)再二次释放就不会有问题!

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

这种错误就很严重了!我们总是申请不释放:就会造成内存泄漏,导致死机卡顿?;比如一个常见的现象:我们的服务器一般是不关机的,一直在跑;如果我们在跑的东西一直在申请空间确没有释放,过段时间就会使服务器宕机;然后重启后就会好了!

补充:动态开辟的空间,2种回收方式:(1)主动free?? ??? ?(2)程序结束

6. 几个经典的笔试题 ??6.1??题目1:

问题1:?

str传给GetMemory函数的时候是值传递,所以GetMemory函数的形参p是str的一份临时拷贝,在GetMemory函数内部动态申请空间的地址,存放在p中,不会影响外边str,所以当GetMemory函数返回之后,str依然是NULL。所以strcpy拷贝会失败。

问题2:?

没有free释放,当GetMemory函数返回之后,形参p销毁,使得动态开辟的100个字节存在内存泄漏。

??修改方法1:

我们就改成传址调用,利用二级指针接收!这样对形参的改变就会影响实参,此时把动态申请的空间交给指针p,其实就是给str!最后用完还要进行释放!

??修改方法2:

不用传址调用怎么办呢?这就需要利用return指针返回来,这时我们还是用一级指针接收!因为是动态开辟的空间实际上是在堆区上开辟的,需要手动才销毁;我们把这个地址利用return带回来,然后在利用一个指针就接收就可以了!

比如:这种方法就可以应用在带头循环双链表中给头结点开辟空间,就可以利用return返回值的方式,避免使用二级指针!

??6.2? 题目2:

问题:?

同样是利用return返回值的方式,这里为什么就不行了呢?GetMemory函数内部创建的数组是在栈区上创建的,出了函数,p数组的空间就还给了操作系统,返回的地址是没有实际意义的,如果通过返回的地址,去访问内存就是非法访问内存!

??6.3? 题目3:

?问题:

看着好像是没什么问题,是传址调用,二级指针接收;主要就是,没有释放空间!free(str),str = NULL!

??6.4? 题目4:

问题:

str已经释放了,下面就不能在访问,在访问就属于非法访问内存;为了避免这种错误发生:free过后一定要手动置空 str=NULL

??6.5? 题目5:

问题:?

(1)x是局部变量,在栈区上创建,出了这个范围,就把内存释放了,return(&x)没有实际意义! (2)指针ptr才开始没有初始化,是野指针,里面放的随机值!

??6.6? 经典例题补充

解析:?

(1)对于#define定义符号,相当于替换,所以是int*a,b===>*说明a是指针,类型是整型;对于b只能说明是整型!所以如果我们要是一下子定义两个指针int*a,*b要写成这种形式。 (2)typedef是类型重定义,代表着一种类型;把int*起个别名叫int_ptr;所以c,d都是指针类型

7.?C/C++程序的内存开辟

C/C++程序内存分配的几个区域: 1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。 2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。 3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。 4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。补充:static关键字修饰局部变量的例子解释 实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁所以生命周期变长1

8. 柔型数组

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

8.1 柔性数组的特点

(1)结构中的柔性数组成员前面必须至少一个其他成员。

(2)sizeof 返回的这种结构大小不包括柔性数组的内存。 (3)包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

(4)柔型数组开辟的空间都是在堆上开辟的!

??例:

8.2 柔性数组的使用 ??计算大小

??柔性数组的动态创建

?(1)对于柔型数组的动态开辟空间,是用结构体指针(struct S* ps)进行接收;对于大小主要包括两部分:

? ? ? ? 一是n的大小创建,我们就用sizeof(struct S),这算的就是除柔性数组以外的大小;

? ? ? ? 二是柔型数组的动态开辟;其实就是开辟10个整型,我们就用10*sizeof(int);

(2)成功开辟后就使用,通过指针ps来查看内存发现已经被赋值;

(3)用完了,我们就用realloc进行扩容,这也就体现了柔性数组的特点,大小是可变的;我们用一个新的指针ptr进行接收,扩容成功后;把新指针ptr赋值给旧指针ps;

(4)最后在进行ps指针的释放,free(ps),ps = NULL ;

??利用指针实现柔型数组的功能

为了保证和柔性数组一样的性质,都是在堆上开辟的!我们所有的都需要用malloc开辟空间;柔型数组是一个malloc整体一块开辟的;用指针代替柔性数组,就要分开开辟:一个开辟除指针以外的大小的而空间;一个开辟指针所需大小的空间!

?(1)利用指针代替柔型数组;实际上就是从一步到两步的过程;柔性数组只需要一次malloc;而指针形式就需要两次malloc;两次malloc就需要两次free;

(2)第一次malloc是动态创建除指针以外所需的空间;第二次malloc是动态创建指针所需大小的空间;

(3)成功开辟后就使用,通过指针ps->arr来查看内存发现已经被赋值;

(4)用完了,我们就用realloc进行扩容,我们就只需要对指针部分进行扩容;我们用一个新的指针ptr进行接收,扩容成功后;把新指针ptr赋值给旧指针ps->arr;

(5)最后在进行ps->arr指针和ps指针的释放,free(ps->arr),ps->arr?= NULL ;free(ps),ps?= NULL

9. 习题补充 ??9.1?习题1

看着图形很简单,就打印两个对角线的位置;但是也要从中找到不变的量:一个对角线是i == j这个很容易看出来,另一条对角线呢?其实就是 i+j = n-1!

??9.2?习题2

首先我们要理清楚规律:1、3、5、7、8、10、12?是31天;4、6、9、11是30天;如果是平年2月份是28天,如果是闰年二月份是29天!31,28,31,30,31,30,31,31,30,31,30,31!

我们发现对于月份好像也没什么规律,都是有奇数也有偶数;数据也不多,不妨就写成数组的形式!

??9.3?习题3

我们在插入的时候,就面临一个问题:从前往后插还是从后往前插?如果从前往后插我们移动数据时就会造成数据的覆盖;所以我们就从后往前插入!


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。