C语言进阶第七篇【动态存储和柔性数组】(上)

简介: C语言进阶第七篇【动态存储和柔性数组】(上)

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


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


(1)栈区:局部变量,函数形参===》临时使用,不要开辟多大的空间!

(2)堆区:动态内存开辟(malloc free realloc calloc)!

(3)静态区(数据段):全局变量,静态变量!


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


1. malloc

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


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

          (1)如果开辟成功,则返回一个指向开辟好空间的指针。

          (2)如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

⭐️返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

⭐️如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。


❤️例:malloc使用

10a1303c98544be18105ccaf28a0e74e.png


⭐️静态开辟空间的方式

e0e7e2021d6645df938ef0a822811b88.png


(1) 空间开辟大小是固定的。

(2)数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。


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

⭐️动态开辟空间的方式

3bcea3e4290e492eb36e6a73c2c9f435.png

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


(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使用

bd6d7f367d234547baf1b2dee92dc5ab.png


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

a8b42cffb1294b71806bb6046a5635d5.png

我们动态创建一个空间用指针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

82cd2c148d464777955a7d54d2f3b122.png



(2)对于calloc

17bf7dfc47834b59aab2426321748190.png



4. realloc

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


❤️void* realloc (void* ptr, size_t size)


⭐️ptr是要调整的内存地址;size 调整之后新大小;返回值为调整之后的内存起始位置。

⭐️realloc在调整内存空间的是存在两种情况:

    (1)后面的空间足够,返回旧的地址。


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


                                          2. 把原来的内容拷贝下来;

                                          3. 把原来的空间释放掉;


                                          4. 把新空间的地址返回去;

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

9a9cda295df340178fd79cda4de8a18c.png



❤️例:realloc使用


00735c729b674ee6a8eca115b7acde36.png


⭐️第一步先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使用效果类似


c4792f033ab64dbe81d888507f50a8f5.png

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


5. 常见的动态内存错误

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


12f78108937246528089e48967ee71ce.png


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


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


d9edc913d24241cd9d597180e052ab05.png


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


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


0b95050b51594eb6b97309b6b28ecdfc.png


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


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


2981875e79584eab8d3470260065ed48.png


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


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


33e1ce8e840b4eb09d08458e31fd35fe.png


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


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


59f05bc49c7a49deb27981ff9cbcecf2.png

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


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


相关文章
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
374 0
|
2月前
|
存储 编译器 C语言
C语言存储类详解
在 C 语言中,存储类定义了变量的生命周期、作用域和可见性。主要包括:`auto`(默认存储类,块级作用域),`register`(建议存储在寄存器中,作用域同 `auto`,不可取地址),`static`(生命周期贯穿整个程序,局部静态变量在函数间保持值,全局静态变量限于本文件),`extern`(声明变量在其他文件中定义,允许跨文件访问)。此外,`typedef` 用于定义新数据类型名称,提升代码可读性。 示例代码展示了不同存储类变量的使用方式,通过两次调用 `function()` 函数,观察静态变量 `b` 的变化。合理选择存储类可以优化程序性能和内存使用。
157 82
|
5月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
1月前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储
|
2月前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
388 8
|
1月前
|
存储 C语言
C语言中的浮点数存储:深入探讨
C语言中的浮点数存储:深入探讨
|
2月前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
2月前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
2月前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
2月前
|
存储 缓存 程序员
c语言的存储类型-存储类
本文详细介绍了C语言中的存储类型及其分类,包括基本类型(如整型、浮点型)和复合类型(如数组、结构体)。重点讲解了不同存储类别(`auto`、`static`、`register`、`extern`、`typedef`、`volatile`、`const`)的特点及应用场景,并展示了C11/C99引入的新关键字(如`_Alignas`、`_Atomic`等)。通过示例代码解释了每个存储类别的具体用法,帮助读者更好地理解和运用这些概念。