C语言 :学习动态内存分配
文章目录
- C语言动态分配
- 为什么存在内存分配?
- 动态内存函数的介绍
- `malloc`
- `free`
- `free`函数的**作用原理**是:
- 使用后将指针赋为`NULL`
- `calloc`
- 运用一次`calloc`函数
- `realloc`
- 当第一个参数为空指针时:
- 当第一个参数不为空指针的时候:
- 常见的动态内存分配错误
- 对空指针的解引用操作
- 对动态开辟空间的越界访问
- 对非动态内存使用free释放
- 对同一块动态内存进行多次释放
- 忘记释放动态空间
- 柔性数组
- 柔性数组的特点
- 一个如何开辟包含柔性数组成员的结构
- 柔性数组的优势
C语言动态分配
为什么存在内存分配?
在声明数组的时候,必须用一个编译时常量来指定数组的长度,但是数组的长度往往在编译的时候才能确定,这样可能会有遇到下面这两种情况:
1.有可能数组长度不够。
2.可能会浪费大量的空间。
例如:如果存在一个通讯录,通讯录的大小事先已经确定(1000),对于一些人可能只会使用100个单位,
对于一类人,可能会认为1000个单位不够使用
我们可以使用动态内存分配来解决这个问题,动态内存可以随时开辟一段空间,并且可以随时根据要求去调整该空间的大小。
不过需要注意:但是动态内存分配只能调整动态开辟出来的空间大小,并不能随意调整其他空间的大小,因为动态内存分配的空间和其他变量分配的空间不属于同一块空间
动态内存函数的介绍
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc这4个函数
malloc
该函数的头文件:stdlib.h
该函数的原型
void* malloc (size_t size);
//void类型的返回值类型利于我们返回任意类型的指针
//size_t 单位是字节,意味着我们会申请出一块以字节为单位的空间
注意:
该函数的作用是在内存中的动态内存区分配一个长度为size的连续空间。
如果开辟空间成功,会返回出这个新空间的首字节地址。
如果开辟空间失败,函数会返回一个空指针。
下面我们来使用一次该函数(我们使用指针来接收返回值):
#include<stdio.h>
#include<stdlib.h>
int main()
{//开辟十个整形的空间int * p = (int *)malloc(40);//应该尽量强制转换成接收指针的类型,这样更规范。return 0;
}
由于我们不知道这个空间是否开辟成功,所以我们需要判断返回的指针是否是空指针。
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>int main()
{//开辟十个整形的空间int * p = (int *)malloc(40);if(NULL == p){printf("%s\n",strerror(errno));//使用sttrerror 函数可以打印出开辟空间错误的原因。return 0;//如果开辟空间失败就退出。}return 0;
}
在空间开辟成功后,我们就可以访问该空间了(我们可以使用指针来访问)
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{//开辟十个整形的空间int * p = (int *)malloc(40);if (NULL == p){printf("%s\n", strerror(errno));return 0;}//使用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d\t", *(p+i));}//释放free(p);p = NULL;return 0;
}
上面的代码段最后我们使用了free
函数,使用该函数的目的是释放掉刚才所申请的空间。下面详细的介绍该函数的使用:
free
该函数的原型是:
void free (void* ptr);
该函数的参数是指向由动态内存开辟的空间(包括malloc
、 calloc
、 realloc
开辟)的指针
free
函数的作用原理是:
使用该函数后,参数的那一个指针指向空间的操作(访问)权限归还给操作系统,我们不能再次使用该空间
free
函数的使用
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>int main()
{//开辟十个整形的空间int * p = (int *)malloc(40);//尽量使用强制类型转换if(NULL == p){printf("%s\n",strerror(errno));return 0;}//使用这段空间//...//释放这段空间free(p);p = NULL;return 0;
}
使用后将指针赋为NULL
在实际操作中,我们只使用free函数将该空间释放是不够的。分析该段代码:我们在申请空间的时候创建了一个指针变量用来接收返回的地址,但是在释放了动态空间后,该指针仍然指向那一块空间。这会造成非法访问(因为我们已经没有权限再次使用那块空间),所以我们在释放空间后需要将该指针变量赋为空指针,这样更安全。
注意:
每次使用完动态开辟的空间后都需要将该空间释放。如果没有释放,那么那一块空间将会在程序结束的时候才会释放(程序没有结束的时候该空间就不能有其他的作用)。
calloc
我们已经知道malloc
函数可以开辟空间,其实不止这一个函数,我们还有两个函数也可以开辟空间,其中一个是calloc
该函数的使用方法和malloc
不同
该函数的原型是:
void* calloc (size_t num,size_t size);
//第一个参数是 开辟元素的个数
//第二个参数是 每一个元素的大小
除了参数不同外,该函数还有一个特点:在开辟空间后会将这个空间全部初始化为0
,然后才返回该空间的起始地址
运用一次calloc
函数
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{//开辟十个整形的空间int * p = (int *)calloc(10,sizeof(int));if (NULL == p){printf("%s\n", strerror(errno));return 0;}//使用int i = 0;for (i = 0; i < 10; i++){printf("%d\t", *(p+i));}//观察初值printf("\n");for (i = 0; i < 10; i++){*(p + i) = i;}//重新赋值for (i = 0; i < 10; i++){printf("%d\t", *(p+i));}//观察赋值后的情况//释放free(p);p = NULL;return 0;
}
我们可以看到在观察初值的时候,每一个值都是0,这也验证了前面说的calloc
函数会在开辟好空间后将整个空间全部初始化为0
.
realloc
该函数的原型
void* realloc(void* ptr, size_t size);
//第一个参数是一个指针,指向由动态内存开辟出来的空间的起始地址
//如果第一个参数是空指针,那么该函数的使用方法和malloc相同
//第二个参数 要访问的空间的大小
该函数的特点:
- 该函数既可以开辟动态空间(第一个参数为
NULL
),也可以调整动态空间(第一个参数不为NULL
).
当第一个参数为空指针时:
使用方法和malloc一样:开辟成功的话会返回新空间的首地址,开辟失败会返回空指针
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{//开辟十个整形的空间int* p = (int*)realloc(NULL, sizeof(int) *10);if (NULL == p){printf("%s\n", strerror(errno));return 0;}//使用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}//重新赋值for (i = 0; i < 10; i++){printf("%d\t", *(p + i));}//观察赋值后的情况//释放free(p);p = NULL;return 0;
}
当第一个参数不为空指针的时候:
可以调整前面申请的空间的大小:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{//开辟十个整形的空间int* p = (int*)malloc( sizeof(int) *10);//第一次申请空间if (NULL == p){printf("%s\n", strerror(errno));return 0;}//使用int i = 0;int* ptr = realloc(p, sizeof(int) * 20);//利用realloc将前面申请的空间扩大一倍if (NULL == ptr){printf("%s\n", strerror(errno));return 0;}p = ptr;for (i = 0; i < 20; i++){*(p + i) = i;}for (i = 0; i < 20; i++){printf("%d\t", *(p + i));}//释放free(p);p = NULL;return 0;
}
我们注意到在使用realloc函数的时候我们仍然会用一个指针去接收返回值的,不过,为什么我们不直接用第一次申请空间的指针区接收这个返回值?
我们需要先来了解realloc函数使用时会遇到的几种情况:
情况1:第一次申请的空间后面有足够的空间,该函数就在这段空间后面开辟新的空间,
情况2:第一次申请的空间后面没有足够的空间,该函数会在一个足够的位置创建
情况3:没有满足条件的内存空间,就会返回一个空指针
现在我们就能理解为什么我们在使用realloc函数区调整一个空间的时候会去利用一个新的指针变量:
如果我们两次使用同一个指针变量,那么当使用realloc函数返回空指针的时候,我们的指针变量就被赋值为NULL,
无法再指向我们原来的数据,我们也无法再找到之前的数据
常见的动态内存分配错误
对空指针的解引用操作
#include<stdio.h>
#include<limits.h>
#include<stdlib.h>
int main()
{int* p = malloc(INT_MAX);int i = 0; for (i = 0; i < 10; i++){*(p + i) = i;}return 0;
}//这段代码向内存申请了一段很长的连续空间,内存无法相应该需求,就会返回空指针,此时我们利用空指针去访问空间按,会造成非法访问。
所以我们应该在申请空间后判断是否为空指针,利用这一个代码段
int* p = malloc(INT_MAX);
if(NULL == p)
{printf("%s",strerror(errno));//出现问题还可以打印出问题的原因在结束程序。return 0;
}
对动态开辟空间的越界访问
我们在申请空间之后应该清楚的知道该空间有多大,不能访问我们申请的空间之外的内容
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{char * p = (char *)malloc(10);if(NULL == p){printf("%s",strerror(errno));//出现问题还可以打印出问题的原因在结束程序。return 0;}int i = 0;for(i = 0; i <= 10; i++)//在访问*(p+10)的时候会造成内存非法访问{*(p+i) = 'a'+i;}for(i = 0; i <= 10; i++){printf("%c ",*(p+i));}free(p);p = NULL;return 0;
}
对非动态内存使用free释放
free只能释放用动态内存开辟的空间
int main()
{int a = 10;int p = &a;...free(p);p = NULL;//使用的时候应该注意不能将普通变量free return 0;
}
使用free去释放动态内存开辟空间的一部分
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{char * p = (char *)malloc(40);if(NULL == p){printf("%s",strerror(errno));return 0;}//将前五个数据初始化为1 2 3 4 5int i = 0;for(i = 0; i <5;i++){*p = i+1;p++;}free(p);//这时候指针p不在指向开辟的空间的起始位置p = NULL;return 0;
}
这是无法实现的,在释放动态开辟空间的时候,只能释放整个空间(free参数只能时前面开辟的空间的起始地址);
对同一块动态内存进行多次释放
第一种:在p 赋为空指针后再次释放,不会报错
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{char * p = (char *)malloc(40);if(NULL == p){printf("%s",strerror(errno));return 0;}//...//...free(p); p = NULL;free(p);return 0;
}
第二种:连续释放了两次指针p(中间没有将p赋值为空指针) ,会出现错误。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{char * p = (char *)malloc(40);if(NULL == p){printf("%s",strerror(errno));return 0;}//...free(p); //...free(p);return 0;
}
所以我们如果每次释放完该控件,就去将指针赋值为空指针,就不会出现这个问题
忘记释放动态空间
我们申请的空间如果不能及时释放,那么该空间就只会在该程序结束的时候才会被自动释放,在此之前,这块空间无法再被利用,会造成内存泄漏。内存泄漏会增加程序的体积,可能会导致系统或者程序崩溃。
柔性数组
在C99中,结构的最后一个元素允许时未知大小的数组,这就叫做柔性数组成员
#include<stdio.h>
struct S{int n;int arr[];//也可以这样使用int arr[0];
};
//结构的最后一个成员时数组,并且这个数组没有指定大小,这个成员就是柔性数组成员int main(){return 0;}
柔性数组的特点
结构中柔性数组成员前面至少应该还有一个其他成员
柔性数组大小不纳入结构体大小的计算
包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预取大小
一个如何开辟包含柔性数组成员的结构
我们得到了这样一块动态内存空间,前面了解到我们可以通过realloc
函数去调整动态内存分配的空间,所以我们可以使用realloc
函数来动态调整柔性数组的大小
记住:在使用完后仍然需要释放动态分配的空间
柔性数组的优势
有利于释放
访问速度更快
C语言 :学习动态内存分配相关推荐
- C语言中动态内存分配的本质是什么?
摘要:C语言中比较重要的就是指针,它可以用来链表操作,谈到链表,很多时候为此分配内存采用动态分配而不是静态分配. 本文分享自华为云社区<[云驻共创]C语言中动态内存分配的本质>,作者: G ...
- c语言malloc引用类型作参数,c语言中动态内存分配malloc只在堆中分配一片内存.doc...
c语言中动态内存分配malloc只在堆中分配一片内存 .C语言中动态内存分配(malloc)只在堆中分配一片内存,返回一个void指针(分配失败则返回0),并没有创建一个对象.使用时需要强制转换成恰当 ...
- 【C语言】动态内存分配
[C语言]动态内存分配 文章目录 [C语言]动态内存分配 一.malloc 与free函数 二.calloc 三.realloc 四.常见的动态内存的错误 本期,我们将讲解malloc.calloc. ...
- 【C语言进阶深度学习记录】三十三 C语言中动态内存分配
如何在程序运行的时候动态给程序分配内存? 文章目录 1 动态内存分配的意义 1.1 C语言中如何动态申请内存空间 1.2 malloc和free的用法 1.3 calloc与realloc 1.31 ...
- 【C语言】------ 动态内存分配
动态内存开辟详解 动态内存分配 什么是动态内存分配? 一.为什么使用动态内存分配呢? 二.动态内存函数 1.malloc和free 2.calloc和realloc 三.常见的动态内存错误 1.对`N ...
- C语言:动态内存分配
个人博客网址:https://ljsblog.com 动态内存分配(十) 在不知道所需要的空间大小的情况下,这时就可以使用动态内存开辟. 当开辟的空间不再使用时,用free函数来释放calloc.ma ...
- 【C语言】动态内存分配详解
目录 一.为什么有动态内存分配 二.动态内存分配函数 (1)malloc()函数 (2)calloc()函数 (3)realloc()函数 三.常见的动态内存错误 1.越界访问 2.内存泄漏 3.对N ...
- C语言:动态内存分配+经典面试题
前言: 通常,我们在栈空间开辟的内存都是固定的,这是十分不方便使用的.为了更加灵活的分配和使用内存,我们要学习C语言中一些常用的与内存分配相关联的函数.顺便,我们会补充数组中柔性数组的知识. 内存分区 ...
- C/C++——动态内存分配
动态内存分配,就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法.动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序 ...
最新文章
- 从PRISM开始学WPF(四)Prism-Module?
- html figure标签并排显示,html figure标签怎么用
- Python数据分析笔记——Numpy、Pandas库
- CentOS7命令(基本命令,新手入门)
- 元璟资本陈洪亮解析人货场融合 消费者变成“合作者”
- python笔记全_Python笔记
- “猜心思”的Hard模式:问答系统在智能法律场景的实践与优化
- NumPy 简单应用
- CF 504E Misha and LCP on Tree——后缀数组+树链剖分
- Linux命令任务管理器,如何在:Linux下面启动任务管理器
- 实验十三:配置STP、RSTP以及负载均衡(生成树负载均衡)
- 微信小程序退款功能(详解完整)
- 王者荣耀微信登陆不了服务器,王者荣耀微信区怎么登陆不了 王者荣耀微信区怎么登不上...
- 建站过程中,网站优化的雷区
- Unity摄像机对象锁定旋转运镜模拟
- 秀米中如何添加链接、文件链接、小程序链接?
- 九连环问题(Java)
- Ubuntu 21.04 如何进入命令行的登录界面
- 盘点三种卫星图分幅导出的方法
- Day09.面向对象进阶