目录
前言
随着我对C语言的深度学习,对于开辟数组需要提前设定好数组大小越发觉得不方便,这时就有了动态内存开辟。
一、动态内存开辟的原因
我们平时常见的内存开辟方式有
int val;
char arr[20];
如果是全局变量它就在静态区开辟空间,如果是临时变量它就在栈区开辟空间,但是我们知道内存中有——栈区,堆区,静态区。这时有人会说什么数据存储在堆区?
这就引出了我们今天的主题动态内存开辟的变量存储在堆区。
我们知道静态变量有以下缺点:
二、动态内存开辟函数
1.malloc函数


malloc函数是最基本的动态内存开辟的函数,它的返回类型是void * 参数是size_t类型,传过去的参数是我们想要开辟空间的大小,单位是字节。如果空间开辟失败,它会返回空指针NULL,开辟成功会返回开辟空间的起始地址,注意!!malloc申请的内存存的是随机值。如果我们想要用那块空间,我们们就需要强制类型转换为我们想要的变量类型的指针,如果我们不进行强制类型转换 ,解引用一个void *的指针是非法的,所以我们必须进行强制类型转换。
例子:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
}
我们这就在堆区开辟了40个字节的空间,一个int型变量占用4个字节,相当于开辟了一个有10个元素的数组,每个元素都是int型的。动态内存开辟与数组特别像。
注意:动态内存开辟开辟的是一块连续的内存空间,同时我们需要主动释放申请的内存空间
看到这里,我们会比较疑惑,动态内存开辟不是内存大小不用预先指定大小吗?
我一开始也是对这一点比较迷惑,说好的是动态内存开辟,结果还是自己设定大小。
其实不然,这样的动态内存开辟虽然不如C++中的string可以自己伸长数组大小,但是它也是有应用场景的。我拿我写过的代码来举例

这是一个有关通讯录的代码,我们发现malloc的参数是一个数据的大小与一个变量或者数字相乘得到的,我们就可以根据设定的变量的大小从而来控制开辟的空间大小。
我们注意到调用了malloc函数之后立马判断了指针是否为NULL,同时用perror函数打印哪个函数出错的信息,并且在一进入函数就判断传入的地址是否为空。这是一个比较好的习惯。
我们假想一个极端情况,如果开辟0个字节,具体情况取决于编译器
2.free函数
既然我们可以申请内存,那么我们也可以释放内存,在一个程序中我们用动态开辟内存函数开辟空间后,我们就要主动释放申请的那片空间,如果我们一直申请而从不释放,就会造成内存泄漏,不断消耗堆区空间,如果我们写了一段程序放在服务器上,让它7*24小时不停的跑,终有一天内存被消耗完,所以这是一件恐怖的事情。我们要知道一件事情,当一段程序结束运行时,系统会自动地收回被申请的那段空间。我们想要释放空间就会用到free函数

free函数的参数是指向动态开辟空间首元素地址的指针,我们一定要保存好这个指针,我们一般不会去修改这个指针。我们声明这个指针为p;
我们释放空间之后,p中存放的内容是不变的,即p还是指向我们原来申请的空间的起始地址,这是十分危险的一件事,释放空间之后,我们不知道那块空间存放的是什么,我们如果解引用p就会造成非法访问,p就是一个野指针。
因此使用完空间后,将p置为NULL是一个十分关键的操作。
如果malloc返回一个空指针,则free函数什么都不会发生
如果不主动释放申请的空间,程序结束后,空间由操作系统自动回收
如果程序不结束,动态内存不会自动回收,形成内存泄漏
3.calloc


我们发现calloc与前两个函数最大的不同是它有两个参数size_t num和size_t size
num是申请的元素的个数,size是元素大小
calloc主要是用来申请一个数组,与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
例
#include<stdio.h>
#include<stdlib.h>
int main()
{
//int* p = (int*)malloc(40);
int* p = (int*)calloc(10, sizeof(int) * 10);
return 0;
}
4.realloc

我们直到在英语中re代表的是在,又,顾名思义realloc是用来扩展我们的动态内存的,realloc函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
memblock是用来拓展的空间起始地址,size是扩展之后这块空间的字节大小
如果拓展失败,它会返回空指针,这时候我们要注意,先创建一个临时变量,将realloc的返回值赋值给它,然后判断这个指针是否为NULL,如果不为NULL,我们就将返回值赋值给memblock,因为动态开辟空间的起始位置是十分关键的,如果弄丢这个地址会导致无法释放空间。如果realloc函数拓展失败就会返回NULL,这样就会把起始位置的地址弄丢。
我们在原空间的基础上继续向后拓展,如果空间不够大,realloc会重新在内存中找一块空间并且将数据拷贝到新的空间,并且将原来的空间free掉,扩容失败会返回空指针。
总结
动态内存开辟一定要检查指针类型
free之后一定要将指针置空(NULL)
一定要注意不要越界访问
不要对非动态开辟内存使用free
不要free动态开辟内存的一部分
必须从动态开辟内存的起点释放
不要对同一块内存空间多次释放
三、C/C++内存开辟

四.柔性数组
1.柔性数组定义
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
这是两个柔性数组定义的方式
柔性数组前面必须有其它成员变量
计算大小时不算柔性数组的大小
2.柔性数组的优势
总结
以上就是今天要讲的内容,感谢大家的支持。
503

被折叠的 条评论
为什么被折叠?



