【C语言进阶】动态内存管理(1)
目录
1. 引言
2. malloc函数
2.1 malloc的使用
2.2 与正常开辟空间的区别
存储区域不同
2.3 变长数组可变?
2.4 free是做什么的?
2.4.1 关于free的注意事项
本章节内容来介绍c语言的动态内存管理,可能内容较多这里分为多个博文来分别描述,动态内存管理是C语言进阶部分中比较重要的内容,重要程度不亚于指针。
1. 引言
在C语言中,申请内存空间一般就是一次申请,不能更改,例如下面就申请了固定的字节。
int main()
{int a = 10; // 4个字节int arr[10]; // 40个字节return 0;
}
再例如之前写的结构体项目:通讯录使用的就是结构体数组,里面固定存放了100个结构体,这就会造成一个问题,如果我只有20个联系人就会浪费掉80个空间,如果我有120个联系人,空间就会不够;这种实现方式不够灵活,所以我们希望能够自己维护自己的内存空间。
本质上需要理解四个函数:malloc、calloc、realloc、free。
2. malloc函数
查看函数的描述:这个函数能够开辟一块内存空间,返回这块空间的起始指针(地址);这个函数的形参是字节数,返回值是void*,需要强转成自己需要的类型。
在申请空间的时候,也要考虑到开辟失败的可能性,一旦开辟失败,返回的是空指针。、
2.1 malloc的使用
当使用malloc函数的时候需要注意,这里是用作动态开辟int类型的数组,为了以后方便操作所以需要对返回值进行强制类型转换成自己所需要的类型。
strerror的头文件是string.h;
errno的头文件是errno.h;
malloc的头文件是stdlib.h
这里的errno是错误码,strerror可以将错误码转换成字符串信息,一旦空间分配失败,就打印错误信息,返回1表示程序非正常结束。
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>int main()
{// 常规方法int arr[10] = { 0 };// 动态内存分配int* p = (int*)malloc(40);if (p == NULL) // 分配空间失败{printf("%s\n", strerror(errno));return 1;}// 使用刚刚的空间int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d\n", *(p + i));}return 0;
}
此时运行没有报错,运行如下:
我们再展示一下开辟内存失败的情况,将内存开辟字节数换成INT_MAX大概是21亿左右:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>int main()
{// 常规方法int arr[10] = { 0 };// 动态内存分配int* p = (int*)malloc(INT_MAX);if (p == NULL) // 分配空间失败{printf("%s\n", strerror(errno));return 1;}// 使用刚刚的空间int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d\n", *(p + i));}return 0;
}
运行结果就是将错误码打印的错误信息(32位运行环境):
2.2 与正常开辟空间的区别
存储区域不同
内存可简单分为栈区、堆区、静态区等,下图可以展示申请空间存储区域的不同:
在堆区申请的空间是可以随意调整大小,在栈区申请空间之后则不可以进行调整。
如果有之前学过C语言的读者就会产生疑问,上面的代码申请了空间,但是没有使用free进行释放空间,这会导致内存泄漏吗?——其实当程序退出之后,系统会自动回收;
什么是内存泄露呢?当申请一块内存空间,使用完毕之后需要“返还”,如果没有返还,相当于“丢了”,这就叫内存泄露。
2.3 变长数组可变?
在C99中存在一个变长数组的内容,输入一个变量,我们可以动态地决定一个数组的长度类似于:
int n = 0;
scanf("%d",&n);
int arr[n];
但是这并不意味着长度可变,还是申请一块固定的内存区域,所以取名有些迷惑。
2.4 free是做什么的?
在free处打一个断点进行调试,我们发现当执行到free语句的时候,此时p存放的地址仍然还是之前的地址,那么free做了什么呢?
free 告诉编译器,这段空间我不用了,仅仅是做了一个声明而已;如果想要真正的释放空间,那么就需要手动置空,如下:
free(p);
p = NULL;
如果只是free了这个指针会导致什么后果?这块地址还是能够被找到,编译器万一给了这块空间一些其他的数据,我们还是可以通过指针p来进行访问,这就非常的不安全,所以如何正确释放内容空间?
①free指针,告诉编译器,这块内存已经不在需要了,可以回收了。
②将指针置空,毁尸灭迹,不要让指针找到以前的地址。
如果一直申请空间,从不释放,那么在程序运行过程中,内存的占用会不断增加,如下图所示:
这段代码已经快占用内存2个GB的空间了, 如果以后在工作中遇到了一个项目一直在服务器中运行,过了一段时间就会崩溃,但是重启之后又好了,这就可以考虑是不是内存泄露的问题了。
2.4.1 关于free的注意事项
①我们首先来看下面的代码:
int a = 10;
int *p = &a;
free(p);
p = NULL;
这样写会报错,这里是开辟因为free只能释放动态开辟的空间(堆空间),而上面的变量a是在栈上开辟的,所以不能使用free释放。
②如果传给free一个空指针,这里什么都不会做。
int *p = NULL;
free(p); // do nothing
本期内容到这就结束了,由于内容过多,下一期会介绍calloc等后续函数以及相关的用法和细节,如果你觉得本期内容不错的话,可以点赞收藏评论,支持一下,你的支撑是我更新的最大动力!!