🌇个人主页:_麦麦_
📚今日名言:“你若爱,生活哪里都可爱。你若恨,生活哪里都可恨。你若感恩,处处可感恩。你若成长,事事可成长。不是世界选择了你,是你选择了这个世界。既然无处可躲,不如傻乐。既然无处可逃,不如喜悦。既然没有净土,不如静心。既然没有如愿,不如释然。”
——丰子恺《豁然开朗》
目录
编辑
一、前言
二、正文
1.内存的分布
3.动态内存函数的介绍
3.1 malloc
3.2free
3.3calloc
3.4realloc
4.常见的动态内存的错误
4.1对空指针的解引用操作
4.2对动态开辟空间的越界访问
4.3对非动态开辟内存使用free释放
4.4使用free释放一块动态开辟内存的一部分
4.5对同一块动态内存多次释放
4.6动态开辟内存忘记释放(内存泄漏)
三、结语
小伙伴们好呀,今天为大家带来的是动态内存的相关知识,主要围绕动态内存管理相关函数和常见错误并伴有一定的题目练习,希望能够为读者们带来一定的收获。
在具体讲解动态内存管理之前,需要先给小伙伴们铺垫一下内存空间的分布(简略版)。在之前的学习中,我们认识了局部变量,全局变量等,那它们在内存中处于何种位置呢?
在内存中可以粗略地分为三大块:栈区、堆区和静态区。栈区中存放的大多是一些局部变量,函数的形式参数;而我们下面所要学习的与动态内存管理相关的函数则存在与堆区;最后的静态区则存放静态变量与全局变量。
在了解完大致的内存分布后,接下来就是动态内存管理了。我们都知道无论是创建一个整形变量亦或是一个指定大小的数组,都是需要向内存申请相应大小的空间,也就是在内存里开辟空间。而目前我们已经掌握的内存开辟方式有:
int val = 20; //在栈空间上开辟四个字节 char arr[20] = { 0 }; //在栈空间上开辟10个字节的连续空间
我们会上述的开辟空间的方式有两个特点:
1.空间开辟大小是固定的
2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译的时候进行分配
但是对于空间的需求,不仅仅是上述的情况,有时候我们所需要的空间大小在程序运行的时候才能知道,那么采用数组的编译时就开辟空间的这种方式就不能满足这种需求。这个时候,就只能试试动态内存开辟了。
而C语言正好提供了与动态内存开辟相关的函数,下面就即将展开相关函数的学习。
void* malloc(size_t size);
这个函数是向内存申请一块连续可用的空间,并返回指向这块空间的指针
●引用的头文件:
●如果开辟成功,则返回一个指向开辟好空间的指针
●如果开辟失败,则返回一个指向NULL指针,因此malloc的返回值一定要做检查
●返回的类型是void*,所以malloc函数并不只知道开辟空间的类型,具体在使用的时候由使用者自己来决定
●如果参数size为0,malloc的行为是标准未定义的,取决于编译器
下面演示一下malloc函数的简单使用:
//malloc函数使用示例 #include#include #include int main() { //申请40个字节,用来存放10个整型 int* p = (int*)malloc(40); if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } //存放1-10 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i + 1; } //打印 for(i = 0; i < 10;i++) { printf("%d ", *(p + i)); } return 0; }
注:当申请完空间后要再主动地返回空间,虽然程序结束操作系统会自动回收,但是如果程序一直不结束,那么这部分空间就会一直被闲置,那么如何回收呢?
void free (void * ptr);
C语言中提供了另外一个函数free,专门是用来做动态内存的释放和回收的。free函数用来释放动态内存开辟的内存。
●如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义
●如果参数ptr是NULL指针,则函数什么事都不做
#includeint main() { //申请40个字节,用来存放10个整型 int* p = (int*)malloc(40); if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } //存放1-10 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i + 1; } //打印 for(i = 0; i < 10;i++) { printf("%d ", *(p + i)); } //free是释放申请的内存 free(p); return 0; }
但是我们发现虽然我们已经用free释放了申请的空间,但是p仍存储着指向我们所申请的空间的地址,也就是存在非法访问的的风险,所以一般情况下,当我们使用free释放动态内存之后,还要将指向那块空间的指针置空。
//malloc函数使用示例 #include#include #include int main() { //申请40个字节,用来存放10个整型 int* p = (int*)malloc(40); if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } //存放1-10 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i + 1; } //打印 for(i = 0; i < 10;i++) { printf("%d ", *(p + i)); } //free是释放申请的内存 free(p); p = NULL; return 0; }
void * calloc(size_t num,size_t size);
除了malloc之外,C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配
●calloc的功能是为num个大小为size的元素开辟空间,并且把空间的每个字节初始化为0
●calloc与malloc的区别只在于在calloc会在返回地址之前把申请的空间的每个字节初始化全0
#includeint main() { int* p = calloc(10, sizeof(int)); if (NULL!=p) { //使用空间 } free(p); p = NULL; }
那么在开辟动态内存空间的时候在mallo和calloc两个函数中该如何选择呢?主要是根据自己的需求来考虑。
●malloc无需初始化直接返回地址——效率比较高
●calloc会自动初始化为0
void * realloc(void * ptr, size_t size);
●realloc函数的出现让动态内存管理更加灵活
●有时我们会发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整,那relloc函数就可以做到对动态开辟内存大小的调整
下面具体来认识下realloc函数
●ptr是要调整的内存地址
●size为调整之后新的大小
●返回值为调整之后的内存其实位置
●这个函数在调整原内存大小的基础上,还会将原来内存中数据移动到新的空间
●ralloc在调整内存空间的时候存在两种情况:
(1)原有空间之后有足够大的空间
(2)原有空间之后没有足够大的空间
那么realloc会如何处理这两种情况呢?
情况1:当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,返回的是原有的起始地址
情况2:当是情况2的时候,原有空间之后没有足够多的空间时,拓展的方法是:在对科技上另找一个合适大小的连续空间来使用,这样的话函数返回的是一个新的内存地址
由于上述的两种情况,当我们使用realloc函数就和之前有一些区别。那么具体该如何使用,具体代码如下:
#include#include #include //realloc的使用方式1 int main() { int* p = (int*)malloc(5 * sizeof(int)); if (NULL == p) { perorr("malloc"); return 1; } //使用 int i = 0; for (i = 0; i < 5; i++) { *(p + i) = 1; } //不够了,增加5个整型的空间 p=realloc(p, 10 * sizeof(int)); //继续使用空间 for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } //释放空间 free(p); p = NULL; return 0; } //realloc的使用方式2 int main() { int* p = (int*)malloc(5 * sizeof(int)); if (NULL == p) { perorr("malloc"); return 1; } //使用 int i = 0; for (i = 0; i < 5; i++) { *(p + i) = 1; } //不够了,增加5个整型的空间 int* ptr = realloc(p, 10 * sizeof(int)); if (ptr != NULL) { p = ptr; } //继续使用空间 for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } //释放空间 free(p); p = NULL; ptr = NULL; return 0; }
第一次使用的小伙伴在使用realloc函数的时候很有可能就是按照第一种方式来写的,而第一种方式和第二种方式的区别在于对realloc函数返回的指针的接受方法不同。前者是直接用第一次开辟的空间的指针来接受,而后者则是使用了一个新指针来接受返回的指针。
那么到底哪种方式更好呢,在认识到realloc函数返回的不同情况下,方式2是一种更好的情况。若是采取方式1,一旦realloc函数扩容失败,就会返回一个空指针,而且会把原来的指针置空,相当于把原来的数据丢失了,可以说是很危险的一种情况,而方式2恰恰避免了这种情况。
在掌握了动态内存函数的使用方法之后,可能有的小伙伴在后续的书写中还会遇到代码错误的问题,以下列举了在使用动态内存函数会出现的常见错误,来帮助大家更好的规避错误,写出优秀的代码。
//对NULL指针的解引用操作 void text1() { int* p = (int*)malloc(INT_MAX / 4); *p = 20;//如果p的值是NULL,就会有问题 free(p); }
//对动态开辟空间的越界访问 void text2() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { exit(EXIT_FAILURE); } for (i = 0; i <= 10; i++) { *(p + i) = i; //当i是10的时候越界访问 } free(p); p = NULL; }
//对非动态开辟内存使用free释放 void text3() { int a = 0; int* p = &a; free(p); //释放错误 }
//使用free释放一块动态开辟内存的一部分 void text4() { int* p = (int*)malloc(100); p++; free(p); //p不在指向动态内存的起始位置 p = NULL; }
//对同一块动态内存多次释放 int main() { int* p = (int*)malloc(100); if (NULL == p) { return 1; } //使用 //释放 free(p); //.. free(p); return 0; }
//动态开辟内存忘记释放(内存泄漏) void text() { int* p = (int*)malloc(100); //使用 //...... } int main() { text(); //.. return 0; }
在这种情况下,一旦第一次忘记释放,第二想再释放都释放不了,除非程序结束自动释放。那么有什么好的方法来避免上述情况吗?
//第一层保障:malloc和free成对使用 void text() { int* p = (int*)malloc(100); if (NULL == p) { return 1; } //使用 free(p); p = NULL; } int main() { text(); //.... return 0; } //第二层:将指向动态开辟内存的指针返回去,谁接受谁后面去释放,所以这种函数一定要写注释 int* text() { int* p = (int*)malloc(100); if (NULL == p) { return 1; } //使用 return p; }
到此为止,关于动态内存管理的讲解就已完成一部分了。
关注我 _麦麦_分享更多干货:_麦麦_的博客_CSDN博客-领域博主
大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!