C语言进阶——动态内存管理(上)

C语言进阶——动态内存管理(上),第1张

C语言进阶——动态内存管理(上),第2张

个人主页:_麦麦_

今日名言:“你若爱,生活哪里都可爱。你若恨,生活哪里都可恨。你若感恩,处处可感恩。你若成长,事事可成长。不是世界选择了你,是你选择了这个世界。既然无处可躲,不如傻乐。既然无处可逃,不如喜悦。既然没有净土,不如静心。既然没有如愿,不如释然。”

                                                                                                          ——丰子恺《豁然开朗》

C语言进阶——动态内存管理(上),第3张

目录

​编辑

一、前言

二、正文

        1.内存的分布

        2.为什么存在动态内存开辟

        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动态开辟内存忘记释放(内存泄漏)

 三、结语


一、前言

  小伙伴们好呀,今天为大家带来的是动态内存的相关知识,主要围绕动态内存管理相关函数常见错误并伴有一定的题目练习,希望能够为读者们带来一定的收获。C语言进阶——动态内存管理(上),第4张

二、正文

        1.内存的分布

        在具体讲解动态内存管理之前,需要先给小伙伴们铺垫一下内存空间的分布(简略版)。在之前的学习中,我们认识了局部变量,全局变量等,那它们在内存中处于何种位置呢?

        在内存中可以粗略地分为三大块:栈区堆区静态区。栈区中存放的大多是一些局部变量,函数的形式参数;而我们下面所要学习的与动态内存管理相关的函数则存在与堆区;最后的静态区则存放静态变量与全局变量

C语言进阶——动态内存管理(上),第5张 

        2.为什么存在动态内存开辟

         在了解完大致的内存分布后,接下来就是动态内存管理了。我们都知道无论是创建一个整形变量亦或是一个指定大小的数组,都是需要向内存申请相应大小的空间,也就是在内存里开辟空间。而目前我们已经掌握的内存开辟方式有:

int val = 20; //在栈空间上开辟四个字节 char arr[20] = { 0 }; //在栈空间上开辟10个字节的连续空间

        我们会上述的开辟空间的方式有两个特点:

1.空间开辟大小是固定的

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

        但是对于空间的需求,不仅仅是上述的情况,有时候我们所需要的空间大小在程序运行的时候才能知道,那么采用数组的编译时就开辟空间的这种方式就不能满足这种需求。这个时候,就只能试试动态内存开辟了。

        3.动态内存函数的介绍

        而C语言正好提供了与动态内存开辟相关的函数,下面就即将展开相关函数的学习。

                 3.1 malloc

void* malloc(size_t size);

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

●引用的头文件:<stdlib.h>

●如果开辟成功,则返回一个指向开辟好空间的指针

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

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

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

        下面演示一下malloc函数的简单使用:

//malloc函数使用示例 #include <stdlib.h> #include <stdio.h> #include <string.h> 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; }

C语言进阶——动态内存管理(上),第6张 

注:当申请完空间后要再主动地返回空间,虽然程序结束 *** 作系统会自动回收,但是如果程序一直不结束,那么这部分空间就会一直被闲置,那么如何回收呢?

                 3.2free

void free (void * ptr);

        C语言中提供了另外一个函数free,专门是用来做动态内存的释放和回收的。free函数用来释放动态内存开辟的内存。

●如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义

●如果参数ptr是NULL指针,则函数什么事都不做

#include <string.h> 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); return 0; }

C语言进阶——动态内存管理(上),第7张 

        但是我们发现虽然我们已经用free释放了申请的空间,但是p仍存储着指向我们所申请的空间的地址,也就是存在非法访问的的风险,所以一般情况下,当我们使用free释放动态内存之后,还要将指向那块空间的指针置空。

//malloc函数使用示例 #include <stdlib.h> #include <stdio.h> #include <string.h> 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; }

                3.3calloc

void * calloc(size_t num,size_t size);

        除了malloc之外,C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配

●calloc的功能是为num个大小为size的元素开辟空间,并且把空间的每个字节初始化为0

●calloc与malloc的区别只在于在calloc会在返回地址之前把申请的空间的每个字节初始化全0

#include <stdlib.h> int main() { int* p = calloc(10, sizeof(int)); if (NULL!=p) { //使用空间 } free(p); p = NULL; }

C语言进阶——动态内存管理(上),第8张 

         那么在开辟动态内存空间的时候在mallo和calloc两个函数中该如何选择呢?主要是根据自己的需求来考虑。

●malloc无需初始化直接返回地址——效率比较高

●calloc会自动初始化为0

     

                 3.4realloc

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

●realloc函数的出现让动态内存管理更加灵活

●有时我们会发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整,那relloc函数就可以做到对动态开辟内存大小的调整

        下面具体来认识下realloc函数

●ptr是要调整的内存地址

●size为调整之后新的大小

●返回值为调整之后的内存其实位置

●这个函数在调整原内存大小的基础上,还会将原来内存中数据移动到新的空间

●ralloc在调整内存空间的时候存在两种情况

        (1)原有空间之后有足够大的空间

        (2)原有空间之后没有足够大的空间

C语言进阶——动态内存管理(上),第9张 

         那么realloc会如何处理这两种情况呢?

情况1:当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,返回的是原有的起始地址

情况2:当是情况2的时候,原有空间之后没有足够多的空间时,拓展的方法是:在对科技上另找一个合适大小的连续空间来使用,这样的话函数返回的是一个新的内存地址

        由于上述的两种情况,当我们使用realloc函数就和之前有一些区别。那么具体该如何使用,具体代码如下:

#include <stdlib.h> #include <stdio.h> #include <string.h>//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恰恰避免了这种情况。

         4.常见的动态内存的错误

        在掌握了动态内存函数的使用方法之后,可能有的小伙伴在后续的书写中还会遇到代码错误的问题,以下列举了在使用动态内存函数会出现的常见错误,来帮助大家更好的规避错误,写出优秀的代码。

                4.1对空指针的解引用 *** 作

//对NULL指针的解引用 *** 作 void text1() { int* p = (int*)malloc(INT_MAX / 4); *p = 20;//如果p的值是NULL,就会有问题 free(p); }

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

//对动态开辟空间的越界访问 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; }

                4.3对非动态开辟内存使用free释放

//对非动态开辟内存使用free释放 void text3() { int a = 0; int* p = &a; free(p); //释放错误 }

                4.4使用free释放一块动态开辟内存的一部分

//使用free释放一块动态开辟内存的一部分 void text4() { int* p = (int*)malloc(100); p++; free(p); //p不在指向动态内存的起始位置 p = NULL; }

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

//对同一块动态内存多次释放 int main() { int* p = (int*)malloc(100); if (NULL == p) { return 1; } //使用 //释放 free(p); //.. free(p); return 0; }

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

//动态开辟内存忘记释放(内存泄漏) 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博客-领域博主
         大家的「关注❤️ + 点赞 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!

C语言进阶——动态内存管理(上),第10张

 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/sjk/13518792.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2024-03-06
下一篇 2024-03-08

发表评论

登录后才能评论

评论列表(0条)

保存