【c语言进阶】动态内存管理知识大全(上)
创始人
2024-05-19 10:29:31
0

在这里插入图片描述

🚀write in front🚀
📜所属专栏:c语言学习
🛰️博客主页:睿睿的博客主页
🛰️代码仓库:🎉VS2022_C语言仓库
🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
关注我,关注我,关注我你们将会看到更多的优质内容!!

在这里插入图片描述

文章目录

  • 前言:
  • 一、动态内存🥺:
    • 1.什么是动态内存分配:
    • 2.动态内存分配的意义:
  • 二、常用的动态内存函数👀:
    • 1. malloc函数:
    • 2.free 函数:
    • 3. calloc 函数:
    • 4. realloc 函数:
      • 4.1.realloc函数开辟空间功能:
      • 4.2.realloc函数重新分配内存块功能:
        • 扩容情况一:
        • 扩容情况二:
  • 三、常见动态内存错误🥹:
    • 1.对 NULL 指针的解引用操作(一定要判断空指针!):
    • 2.对动态内存空间的越界访问:(要检查是否越界!)
    • 3.对非动态内存空间使用 free 函数(free函数只针对堆区的内存!):
    • 4.使用 free 函数释放动态内存空间的一部分(free释放时要从头释放!):
    • 5.对同一块动态内存空间多次释放(一定要记得在释放空间后把指针置空!):
    • 6.不释放动态内存空间(内存泄漏):
    • 总结:

前言:

  在前面的静态库通讯录这篇博客中,我们发现我们申请的空间是一定的,这样一定会存在内存浪费或者内存不够用的情况。数组大小一旦确定好,就会向内存申请一块固定大小的连续空间,后面再想增加或者减少空间是非常麻烦的。而今天要介绍的动态内存管理就会很好的帮助大家解决这一窘境,我们可以根据自己的需求向内存申请空,那接下来就让我们一起来看看动态内存管理都有哪些有趣的知识吧。

一、动态内存🥺:

  我们先来复习一下C语言程序编译环境下的系统内存。
当然,最常见的是这三个:
在这里插入图片描述
  其实实际上有5个:

  • 栈区(Stack):编译系统自动分配释放,主要存放 函数参数,局部变量 等 .
  • 堆区(heap):由程序员分配释放管理,一般由 malloc,new等内部存储函数使用, 如果没收回,程序结束时由操作系统收回。创建堆时,一般在堆的头部 用一个字节存放堆的大小;回收堆时,通过查看这个 字节的内容,可得知需要释放的多大的内存。
  • 全局区或静态区:存放 全局变量静态变量 ,程序结束时由系统释放,分为全局初始化区全局未初始化区
  • 常量区:存放 常量 ,程序结束时由系统释放
  • 程序代码区上面4个区统称数据区:存放运行 或准备运行的程序代码,由系统调度

  举一个栗子🌰大家看一看自己是否认识这些变量所属区域:
在这里插入图片描述

1.什么是动态内存分配:

  所谓动态内存分配,就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。

2.动态内存分配的意义:

  动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小

二、常用的动态内存函数👀:

1. malloc函数:

函数格式

void* malloc (size_t size);

  从它的使用格式中我们可以看出,该函数向堆区申请了一块连续的空间,同时返回的是这块空间的 指针
注意事项

  • 如果开辟成功,则返回一个指向开辟好空间的指针
  • 如果开辟失败,则返回一个 NULL 指针,故我们在使用时一定要仔细检查 malloc 函数的返回值
  • 返回值的类型是 void* ,即 malloc 函数并不了解开辟空间的类型,至于空间的具体类型将在使用时由使用者自己决定
  • 如果参数 size 为 0,则 malloc 函数的行为是标准未定义的,将会取决于编译器。 (正常人不会这么干)
#include 
#include 
#include 
#include int main()
{//申请40个字节,用来存放10个整型int* p = (int*)malloc(40);if (p == NULL)//判断是否申请成功{printf("%s\n", strerror(errno));return 1;//申请失败就直接返回}//存放1~10int i = 0;for (i = 0; i < 10; i++)//malloc申请的是一块连续的内存空间,因此可以把它当成数组来使用。{*(p + i) = i + 1;}//打印for (i = 0; i < 10; i++){printf("%d ", *(p + i));}return 0;
}

  判断是否为空指针特别重要⭐⭐⭐⭐

2.free 函数:

函数格式

void free (void* ptr);

  与 malloc 函数恰好相反,free函数的作用为释放动态开辟的内存,同时没有返回值
注意事项

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。很可能程序崩溃。
  • 如果参数 ptr 是NULL指针,则 free 函数将什么都不会做。
  • 下面的realloc,calloc都需要用free释放空间!

还有一个特别重要的点,我们先来看看下图:
在这里插入图片描述
  通过上图我们发现,free(p)仅仅是把 p 指针指向的这块空间归还给了操作系统,但是 p 指针还是指向这块空间,为了避免之后的非法访问,我们还需把 p 置为空指针

3. calloc 函数:

函数格式

void* calloc (size_t num, size_t size);

  calloc 函数的功能就是:为 num 个大小为 size 的元素开辟一块动态内存空间,并将空间内每个字节都初始化为 0。
与malloc的区别
  与函数 malloc 的区别仅在于 calloc 函数在返回地址前会把申请的空间内每个字节都初始化为 0 ,其它方面完全相同。

#include 
#include 
#include 
#include int main()
{int* p = (int*)calloc(10, sizeof(int));//开辟10个整型大小的空间if (p == NULL){perror("aclloc");//判断是否开辟成功return 1;}//使用int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));}//使用完了,释放空间free(p);p = NULL;return 0;
}
//结果:
0 0 0 0 0 0 0 0 0 0

4. realloc 函数:

函数原型

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

注意事项

  • ptr 指向要调整的内存地址
  • size 是调整之后的大小
  • 返回值是一个指向调整之后内存起始位置的指针
  • 扩容失败,则返回一个空指针这里也要对于空指针进行判断
  • 无论扩容还是开辟,都不初始化

在这里插入图片描述

4.1.realloc函数开辟空间功能:

  如果传给realloc函数的是一个空指针,此时就像调用malloc,而且不会将动态内存空间初始化

void* realloc (NULL, size_t size);

4.2.realloc函数重新分配内存块功能:

  realloc 函数的出现,使得动态内存管理更加的灵活。例如有些时侯我们觉得前面申请的空间太小了不够用,或者我们会觉得申请的空间过大了太浪费,这个时候我们就可以通过使用 realloc 函数对之前开辟的动态内存空间的大小再次进行合理的调整

&emso;  换句话说,正是 realloc 函数才使得动态内存空间真正变得“ 动态 ”起来

  例如,当我们在使用过程中发现我们申请来的动态内存空间不够用时,我们就可以通过使用 realloc 函数来对我们申请来的动态内存空间进行扩容:``

int main()
{int* ptr = NULL;ptr = (int*)malloc(40);int* p = ptr;if (p == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}realloc(ptr, 80);//空间不够用,重新分配更大的空间//将指针ptr指向的空间扩容至80字节free(ptr);PTR = NULL;return 0;
}

  但哪怕是在成功扩容时,也仍会出现两种情况:当前空间与后相邻空间之间的空间是否足够 realloc 函数进行扩容操作。

扩容情况一:

  若空间足够,则直接执行扩容操作,并在扩容完成后返回指向起始位置的指针。

扩容情况二:

  若后续空间不够,则将会在堆区中重新寻找合适的空间(足以容纳下扩容后的全部空间),并将原空间内的数据全部拷贝过来,接着释放原空间,并在扩容完成后返回指向新空间起始位置的指针

int main()
{int* p = (int*)malloc(5 * sizeof(int));//先用malloc申请5个整型大小的内存空间if (p == NULL)//判断是否开辟成功{perror("malloc");return 1;}int i = 0;for (i = 0; i < 5; i++){*(p + i) = 1;//把5个整型全部初始化为1}//不够用了,要再增加5个整型int* ptr = (int*)realloc(p, 10 * sizeof(int));if (ptr == NULL){perror("realloc");return 1;}for (i = 0; i < 10; i++){printf("%d ", *(ptr + i));}free(ptr);p = NULL;ptr = NULL;return 0;
}

三、常见动态内存错误🥹:

1.对 NULL 指针的解引用操作(一定要判断空指针!):

int main()
{int* p = (int*)malloc(5 * sizeof(int));int i = 0;for (i = 0; i < 5; i++){*(p + 1) = 0;}return 0;
}

  上面代码中,在 malloc 执行后,没有对 p 指针进行检查,因为 malloc 也可能失败,失败时会返回一个空指针,如果是这样,那下面就是对空指针进行解引用操作,这样是不合适的。

2.对动态内存空间的越界访问:(要检查是否越界!)

int main()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){perror("malloc");return 1;}for (i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);p = NULL;
}

  上述代码,当 i 等于10的时候就会造成越界访问,越界访问最终会导致程序挂掉.

3.对非动态内存空间使用 free 函数(free函数只针对堆区的内存!):

int main()
{int a = 10;//栈区int* p = &a;free(p);return 0;
}

&emspl 需要注意free针对的是堆区上的空间,而上述代码中的 p 指针指向一个整型变量,整型变量是在栈区申请的空间

4.使用 free 函数释放动态内存空间的一部分(free释放时要从头释放!):

int main()
{int* p = (int*)malloc(5 * sizeof(int));if (p == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 5; i++){*p = 0;p++;}free(p);
}

  上面代码中的 p 指针,在经历过多次 p++ 之后就不再指向这 5 55 个整型的起始地址了,此时再去free 就会出问题。
   避免此类错误的方法是,在使用指针前保存好初始指向,并在进行动态内存释放时释放完整的动态内存空间。

5.对同一块动态内存空间多次释放(一定要记得在释放空间后把指针置空!):

int main()
{int* p = (int*)malloc(10 * sizeof(int));free(p);//...(在中间又进行了很多其它操作之后,忘记了已经释放过动态内存空间,并进行了重复释放)free(p);//重复释放动态内存空间p = NULL;return 0;
}

  如上面代码,在程序的最后对 p 指针 free 了两次,此时程序就会报错。本质原因就是对同一块动态内存多次释放程序会报错。但是如果在第一个 free 的后面把 p 指针赋为空指针,然后再 free§ 就没有任何问题了。

6.不释放动态内存空间(内存泄漏):

void test()
{int* p = (int*)malloc(10 * sizeof(int));if (p != NULL){*p = 20;//判断非空后进行使用}//使用后没有释放动态内存空间,在程序终止前该动态内存空间都将无法被释放,将会逐渐蚕食计算机系统的内存
}int main()
{test();while (1);//为了演示内存泄漏,使程序永不终止return 0;
}

避免此类问题的方法是,永远记住,使用一个释放一个,使用结束立刻释放。

总结:

  通过今天内容的学习,相信各位小伙伴们已经掌握了动态内存的开辟、释放与动态修改,并且已经对动态内存空间的各项使用注意事项有了一定的认知和了解。希望小伙伴们在下去以后,在对动态内存空间的使用和管理中一定要慎之又慎,尽最大可能避免出现类似的相关问题。
  更新不易,辛苦各位小伙伴们动动小手,三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

专栏订阅:
每日一题
c语言学习
算法
智力题
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

在这里插入图片描述

相关内容

热门资讯

那一次我尝到了幸福的滋味作文 那一次我尝到了幸福的滋味作文(精选9篇)  在生活、工作和学习中,大家最不陌生的就是作文了吧,根据写...
打针作文400字 打针作文400字九篇  在日常学习、工作或生活中,许多人都有过写作文的经历,对作文都不陌生吧,借助作...
哦,原来是这样作文550字 哦,原来是这样作文550字  下了几天雨,星期天太阳公公终于露出了灿烂的笑脸,我和妈妈享受着那久违的...
养猪猪的感受作文 养猪猪的感受作文啊哈!!~~~我开始养猪猪啦!!!我好happy啊!!嘿嘿!!!这天,我不清不楚的去...
我的学习态度作文 我的学习态度作文4篇  在生活、工作和学习中,大家都写过作文,肯定对各类作文都很熟悉吧,作文是经过人...
家有喜事优秀作文 家有喜事优秀作文(精选30篇)  在平平淡淡的日常中,大家对作文都不陌生吧,作文根据体裁的不同可以分...
中考作文素材 中考作文素材精选15篇  无论是身处学校还是步入社会,大家都跟作文打过交道吧,作文是通过文字来表达一...
我是一只蚂蚁作文 我是一只蚂蚁作文450字(通用35篇)  在日常学习、工作抑或是生活中,大家最不陌生的就是作文了吧,...
我的祖父作文600字 我的祖父作文600字  在日常生活或是工作学习中,大家总少不了接触作文吧,根据写作命题的特点,作文可...
清明作文500字 清明作文500字(精选63篇)  在平凡的学习、工作、生活中,许多人都写过作文吧,借助作文可以提高我...
有一种爱叫做放手作文 有一种爱叫做放手作文(15篇)  在平平淡淡的日常中,大家都接触过作文吧,借助作文可以提高我们的语言...
我爱我校的作文 我爱我校的作文500字(精选47篇)  在平平淡淡的学习、工作、生活中,大家都不可避免地要接触到作文...
写对未来憧憬的作文 写对未来憧憬的作文  未来的世界会是个什么样子的呢?未来有哪些现在没有的科技呢?以下是小编给大家整理...
理想的力量作文550字 理想的力量作文550字  每个人都拥有梦想,因为它是我们前进的方向,人的一生,不就是为做理想中的自己...
爱的故事作文 爱的故事作文(精选22篇)  在学习、工作、生活中,大家都不可避免地要接触到作文吧,作文一定要做到主...
他笑了作文 他笑了作文(通用108篇)  无论是身处学校还是步入社会,大家都经常看到作文的身影吧,作文是通过文字...
胖胖的我作文200字左右精选... 胖胖的我作文200字左右 第一篇我已经上五年级了,五年级对小学生来说是学习比较紧张的年级了,可我们学...
我找回了快乐作文 我找回了快乐作文(通用5篇)  在日常生活或是工作学习中,大家最不陌生的就是作文了吧,作文是由文字组...
五一作文800字 五一作文800字(精选53篇)  在学习、工作乃至生活中,大家都写过作文,肯定对各类作文都很熟悉吧,...
人之初作文 人之初作文········ 人之初·· 常有人喜欢追忆过去,怀念童年,总觉得今天的人不如过去的善良,...