个人主页:_麦麦_

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

——丰子恺《豁然开朗》

目录

​编辑

一、前言

二、正文

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

三、结语


一、前言

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

二、正文

1.内存的分布

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

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

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

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

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

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

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

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

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

3.动态内存函数的介绍

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

3.1 malloc

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-10int i = 0;for (i = 0; i < 10; i++){*(p + i) = i + 1;}//打印for(i = 0; i < 10;i++){printf("%d ", *(p + i));}return 0;}

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

3.2free

void free (void * ptr);

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

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

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

#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++){*(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-10int 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 int main(){int* p = calloc(10, sizeof(int));if (NULL!=p){//使用空间}free(p);p = NULL;}

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

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

●calloc会自动初始化为0

3.4realloc

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的使用方式1int 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的使用方式2int 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博客-领域博主
大家的「关注❤️ + 点赞 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!