【C语言】—— 动态内存管理

慈云数据 2024-05-28 技术支持 53 0

【C语言】——动态内存管理

    • 一、动态内存管理概述
      • 1.1、动态内存的概念
      • 1.2、动态内存的必要性
      • 二、 m a l l o c malloc malloc 函数
        • 2.1、函数介绍
        • 2.2、应用举例
        • 三、 c a l l o c calloc calloc 函数
        • 四、 f r e e free free 函数
          • 4.1、函数介绍
          • 4.2、应用举例
          • 五、 r e a l l o c realloc realloc 函数
            • 5.1、函数介绍
            • 5.2、应用举例
            • 六、常见的动态内存错误
              • 6.1、对NULL指针进行解引用
              • 6.2、对动态开辟空间的越界访问
              • 6.3、对非动态开辟的内存使用 f r e e free free 释放
              • 6.4、使用 f r e e free free 释放一块动态开辟内存的一部分
              • 6.5、对同一块动态内存多重释放
              • 6.6、动态开辟内存忘记释放(内存泄漏)
              • 七、动态内存经典笔试题分析
                • 7.1、题一
                • 7.2、题二
                • 7.3、题三
                • 7.4、题四
                • 八、柔性数组
                  • 8.1、什么是柔性数组
                  • 8.2、柔性数组的特点
                  • 8.2、柔性数组的使用
                  • 8.3、柔性数组的优势
                  • 九、C/C++中内存区域划分

                    一、动态内存管理概述

                    1.1、动态内存的概念

                      在了解为什么要有动态内存管理之前,我们得先知道动态内存的定义。

                      动态内存是指动态的内存空间,意思就是:能动态开辟的内存空间,动态就是申请了这块空间后,可动态的修改这块空间的大小,根据需要,动态地释放和分配内存空间。

                      

                    1.2、动态内存的必要性

                      为什么要有动态内存呢?

                      既然有动态内存,那与之相对的就是静态内存

                      什么是静态内存呢?其实静态内存我们天天都在用,只是不知道它是静态内存而已

                      

                    下面两种内存开辟方式就是静态内存

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

                      

                    但是静态内存的开辟有两个缺点:

                    • 空间开辟的大小是固定的
                    • 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整

                        但是,在实际需求中,我们往往只有在程序运行时才能知道所需的空间大小,用数组开辟空间,往往容易造成内存溢出(空间开小),或者内存浪费(空间开大),无法满足实际的需求

                        

                        因此,C语言引入了动态内存开辟,让程序员自己可以申请和释放空间,并根据需要,自己调整开辟后空间的大小。这样不仅提高了内存的利用率,也极大地增强了程序的灵活性与扩展性。

                        

                        

                      二、 m a l l o c malloc malloc 函数

                      2.1、函数介绍

                        C语言中提供了一个动态内存分配的函数: m a l l o c malloc malloc

                      在这里插入图片描述

                      功能:向内存申请一块连续可用的空间(可当成数组),并返回指向这块空间的指针

                      • 参数 s i z e size size_ t t t s i z e size size:

                        • 分配的内存的大小,以字节为单位。即开辟 s i z e size size 字节大小的空间
                        • 如果参数为 0 , m a l l o c malloc malloc 的行为是标准未定义的,取决于编译器

                            

                        • 返回值 v o i d void void *:

                          • 返回指向开辟空间的指针,因为 m a l l o c malloc malloc 函数 事先并不知道使用者开辟空间存放什么类型的数据,因此指针为 v o i d void void* 类型,以便能接受所有类型。
                          • 使用者可根据自己的需要,将其强制类型转换成自己所需要的类型,以便能进行解引用操作。
                          • 如果开辟失败,则返回 空指针,因此 m a l l o c malloc malloc 的返回值一定要做检查

                              

                            2.2、应用举例

                            #include
                            int main()
                            {
                            	int* p = NULL;
                            	p = (int*)malloc(10 * sizeof(int));
                            	if (NULL == p)
                            	{
                            		perror("malloc fail");
                            		return 1;
                            	}
                            	return 0;
                            }
                            

                            上述代码是使用 m a l l o c malloc malloc 函数开辟 10 个整型变量的空间,也即 40 个字节的空间

                            • 首先,因为 m a l l o c malloc malloc 函数的返回值是指针,我们需用指针变量 p p p 接收其返回值,创建 p p p 时,并不知道其指向的空间,所以先初始化为 NULL。
                            • 接着,使用 m a l l o c malloc malloc 函数开辟空间,因为我们要存放的是整型变量,而 m a l l o c malloc malloc 的返回值类型为 v o i d void void* 我们通过强制类型转换将其返回类型转换为 i n t int int* ,并用 p p p 来接收
                            • 因为 m a l l o c malloc malloc 函数有可能开辟失败1,只有当返回值不为空的情况我们才使用它,因此需判断 p p p 指针是否为空。若为空,则用 p e r r o r perror perror 函数2打印出错误信息,并返回 13。
                            • 若开辟成功,我们就可以愉快地使用这块空间啦

                              在这里插入图片描述

                                需要注意的是: m a l l o c malloc malloc 开辟的空间并不会将其初始化

                                

                                

                              三、 c a l l o c calloc calloc 函数

                                开辟动态内存空间,C语言还提供了一个函数叫 c a l l o c calloc calloc ,原型如下:

                              在这里插入图片描述

                              • 函数的功能是为 n u m num num 个大小为 s i z e size size 的元素开辟一块空间,并将这块空间初始化为 0
                              • 与函数 m a l l o c malloc malloc 的区别只在于 c a l l o c calloc calloc 会返回地址之前把申请的空间的每个字节初始化为全 0

                                举个例子:

                                #include
                                #include
                                int main()
                                {
                                	int* p = (int*)calloc(10, sizeof(int));
                                	if (NULL != p)
                                	{
                                		int i = 0;
                                		for (i = 0; i  
                                

                                  

                                运行结果:

                                在这里插入图片描述

                                  

                                  

                                四、 f r e e free free 函数

                                4.1、函数介绍

                                  

                                  上述 m a l l o c malloc malloc 函数、 c a l l o c calloc calloc 函数以及后面讲的 r e a l l o c realloc realloc 函数所申请的空间,并不满足作用域的规则。只有当程序退出时,用他们开辟的动态空间才会归还给操作系统,换言之,程序不退出,就不会主动归还空间。这时,我们就需要 f r e e free free函数 来对其主动释放

                                   f r e e free free 函数是专门用于动态开辟的内存空间的释放和回收,声明如下:

                                在这里插入图片描述

                                  

                                f r e e free free 函数用来释放动态开辟的内存

                                • 如果参数 p t r ptr ptr 指向的空间不是动态开辟的,那 f r e e free free 函数的行为是未定义的
                                • 如果参数是 p t r ptr ptr 是NULL指针,则函数什么事都不做
                                • 需要注意的是, f r e e free free 函数不会改变指针所指向的值,释放后它依然指向相同的内存空间。因此我们要手动将释放后的 p t r ptr ptr 置空,避免出现野指针。

                                  m a l l o c malloc malloc、 c a l l o c calloc calloc 以及 f r e e free free 函数的声明都在 中

                                    

                                  4.2、应用举例

                                    

                                  我们来看个例子:

                                  #include
                                  #include
                                  int main()
                                  {
                                  	int* ptr = NULL;
                                  	ptr = (int*)malloc(10 * sizeof(int));
                                  	if (NULL != ptr)
                                  	{
                                  		int i = 0;
                                  		for (i = 0; i  
                                   
                                   

                                  在这里插入图片描述

                                    

                                    那我们来看看下面这种情况行不行呢?

                                  int main()
                                  {
                                  	int* ptr = NULL;
                                  	ptr = (int*)malloc(5 * sizeof(int));
                                  	if (NULL != ptr)
                                  	{
                                  		int i = 0;
                                  		for (i = 0; i  
                                  

                                    当然是不行的,为什么呢?

                                    因为传递给 f r e e free free 函数的是要释放空间的 起始地址

                                    上面函数的 p t r ptr ptr 以及不再指向要释放空间的起始地址了,当然是不行的。

                                    

                                    

                                  五、 r e a l l o c realloc realloc 函数

                                  5.1、函数介绍

                                    

                                    可能有小伙伴问:前面你说动态内存可根据需要,动态调整所开辟空间的大小,但前面介绍 m a l l o c malloc malloc、 c a l l o c calloc calloc 以及 f r e e free free函数 都只是在将动态空间的开辟和释放,如何调整空间的大小呢?别急,我们接下来要讲的 r e l l o c relloc relloc函数 就是完成调整开辟空间的大小的任务的

                                     r e a l l o c realloc realloc 函数的出现让动态内存管理更加灵活

                                    

                                    有时我们会发现之前申请的空间太小了,有时我们又会觉得申请的空间太大了,那为了合理的使用内存,,我们一定会对内存的大小做出灵活的调整。那 r e a l l o c realloc realloc函数 就可以做到对动态开辟内存大小的调整

                                  先来看看 r e l l o c relloc relloc函数 的声明:

                                  在这里插入图片描述

                                  • p t r ptr ptr 是要调整的内存地址
                                  • s i z e size size 是调整之后的大小(可以变大,也可变小)
                                  • 返回值为调整之后的内存起始位置

                                    r e a l l o c realloc realloc 调整内存大小分为三种情况:

                                    在这里插入图片描述

                                    1. 原有空间之后有足够大的空间

                                      如上图: r e l l o c relloc relloc 已经开辟 20 个字节的空间,现在我想扩容到 40 字节,同时原有空间后方空间足够扩展新空间

                                      

                                      此时 r e a l l o c realloc realloc 函数直接在后方追加空间,原来空间的数据不发生变化

                                      

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

                                      还是上面那个图,现在我想将他扩容到 400 字节,很明显,已开辟空间后方没有足够的空间,总不能把别人踢开,自己霸占吧

                                      这时, r e a l l o c realloc realloc 函数就会在堆空间 另外找一个 合适大小的空间。

                                      

                                    具体流程如下:

                                    • r e a l l o c realloc realloc 函数先在堆空间上找一块新的空间,并且满足大小要求
                                    • 后将旧空间的数据拷贝到新空间中
                                    • 接着释放旧空间
                                    • 最后返回新空间的起始地址

                                        

                                      3. 空间调整失败

                                         r e a l l o c realloc realloc 可能出现空间调整失败的情况,此时返回的是空指针

                                        

                                        

                                         r e a l l o c realloc realloc 不仅仅能将空间的变大,还能将空间变小,只需要第二个参数的值小于原空间的大小就好了,因为缩小空间比较简单,这里就不再过多介绍,但需要注意的是,缩小空间可能会造成数据丢失,因此需小心使用

                                        同时 r e a l l o c realloc realloc函数 不仅能调整空间大小,还能完成 m a l l o c malloc malloc函数 的功能:当第一个参数 p t r ptr ptr 传递的是 空指针 时, r e a l l o c realloc realloc 函数就不再是调整空间大小了,你都没空间,我还怎么调。此时 r e a l l o c realloc realloc 函数会 新开辟 s i z e size size 字节大小的空间。

                                        

                                        

                                      5.2、应用举例

                                        看了上面三种情况,大家想一想,应该怎样接收 r e a l l o c realloc realloc 调整之后的返回值呢?

                                        可以直接用原来的指针 p p p 接收吗?

                                        显然是不行,如果 r e a l l o c realloc realloc 调整成功,那确实没问题,但如果失败了呢?此时返回的是空指针。本来 p p p 还维护着原来的空间,现在直接变空指针,那原来的空间再也找不到了,这就造成了内存泄漏。

                                        正确的方法是创建新的指针 p t r ptr ptr 来接收,当 p t r ptr ptr 不为 NULL,再将 p t r ptr ptr 的值传给 p p p

                                        

                                      如下:

                                      #include
                                      int main()
                                      {
                                      	int* p = (int*)malloc(5 * sizeof(int));
                                      	if (NULL == p)
                                      	{
                                      		perror("malloc fail");
                                      		return 1;
                                      	}
                                      	//1 2 3 4 5
                                      	for (int i = 0; i  
                                      

                                        

                                        

                                      六、常见的动态内存错误

                                      6.1、对NULL指针进行解引用

                                        

                                      #include
                                      int main(
                                      {
                                      	int* p = (int*)malloc(INT_MAX);
                                      	*p = 20;//如果p的值是空指针,就会有问题
                                      	free(p);
                                      	p = NULL;
                                      	return 0;
                                      }
                                      

                                        

                                        动态开辟的空间,应该先对返回值进行判断,确保空间开辟成功

                                        上述代码所要开辟的空间太大,开辟失败,返回的是空指针,而下面一句代码对空指针进行解引用,是错误的

                                        

                                      #include
                                      int main()
                                      {
                                      	int* p = (int*)malloc(10 * INT_MAX);
                                      	if (NULL == p)
                                      	{
                                      		perror("malloc fail");
                                      		return 1;
                                      	}
                                      	*p = 20;
                                      	free(p);
                                      	p = NULL;
                                      	return 0;
                                      }
                                      

                                      在这里插入图片描述

                                        

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

                                        

                                      #include
                                      int mian()
                                      {
                                      	int i = 0;
                                      	int* p = (int*)malloc(10 * sizeof(int));
                                      	if (NULL == p)
                                      	{
                                      		exit(EXIT_FAILURE);
                                      	}
                                      	for (i = 0; i 
                                      		*(p + i) = i;
                                      	}
                                      	free(p);
                                      	p = NULL;
                                      	return 0;
                                      }
                                      
                                      	int a = 10;
                                      	int* p = &a;
                                      	free(p);//ok?
                                      }
                                      
                                      	int* p = (int*)malloc(100);
                                      	p++;
                                      	free(p);//p不再指向动态内存的起始位置
                                      }
                                      
                                      	int* p = (int*)malloc(100);
                                      	//···
                                      	free(p);
                                      	free(p);//重复释放
                                      }
                                      
                                      	int* p = (int*)malloc(100);
                                      	if (NULL != p)
                                      	{
                                      		*p = 20;
                                      	}
                                      }
                                      int main()
                                      {
                                      	test();
                                      	while (1);
                                      	return 0;
                                      }
                                      
                                      	p = (char*)malloc(100);
                                      }
                                      void Test(void)
                                      {
                                      	char* str = NULL;
                                      	GetMemory(str);
                                      	strcpy(str, "Hello World");
                                      	printf(str);
                                      }
                                      int main()
                                      {
                                      	Test();
                                      	return 0;
                                      }
                                      
                                      	*p = (char*)malloc(100);
                                      }
                                      void Test(void)
                                      {
                                      	char* str = NULL;
                                      	//传str的地址
                                      	GetMemory(&str);
                                      	strcpy(str, "hello world");
                                      	printf(str);
                                      	//释放动态空间
                                      	free(str);
                                      	str = NULL;
                                      }
                                      
                                      	char* p = (char*)malloc(100);
                                      	return p;
                                      }
                                      void Test(void)
                                      {
                                      	char* str = GetMemory();
                                      	strcpy(str, "hello world");
                                      	printf(str);
                                      	free(str);
                                      	str = NULL;
                                      }
                                      
                                      	char p[] = "hello world";
                                      	return p;
                                      }
                                      void Test(void)
                                      {
                                      	char* str = NULL;
                                      	str = GetMemory();
                                      	printf(str);
                                      }
                                      int main()
                                      {
                                      	Test();
                                      	return 0;
                                      }
                                      
                                      	*p = (char*)malloc(num);
                                      }
                                      void Test(void)
                                      {
                                      	char* str = NULL;
                                      	GetMemory(&str, 100);
                                      	strcpy(str, "hello");
                                      	printf(str);
                                      }
                                      int main()
                                      {
                                      	Test();
                                      	return 0;
                                      }
                                      
                                      	char* str = (char*)malloc(100);
                                      	strcpy(str, "hello");
                                      	free(str);
                                      	if (str != NULL)
                                      	{
                                      		strcpy(str, "world");
                                      		printf(str);
                                      	}
                                      }
                                      int main()
                                      {
                                      	Test();
                                      	return 0;
                                      }
                                      
                                      	int i;
                                      	int a[0];
                                      }type_a;
                                      
                                      	int i;
                                      	int a[];
                                      }type_a;
                                      
                                      	int i;
                                      	int a[0];
                                      }type_a;
                                      int main()
                                      {
                                      	printf("%d\n", sizeof(type_a));
                                      	return 0;
                                      }
                                      
                                      	int i;
                                      	int a[0];
                                      }type_a;
                                      int main()
                                      {
                                      	int i = 0;
                                      	type_a* p = (type_a*)malloc(sizeof(type_a) + 10 * sizeof(int));
                                      	if (NULL == p)
                                      	{
                                      		perror("malloc fail");
                                      		return 1;
                                      	}
                                      	//业务处理
                                      	p-i = 10;
                                      	for (i = 0; i a[i] = i;
                                      	}
                                      	//调整空间
                                      	type_a* ptr = (type_a*)realloc(p, sizeof(type_a) + 20 * sizeof(int));
                                      	if (ptr != NULL)
                                      	{
                                      		p = ptr;
                                      	}
                                      	free(p);
                                      	p = NULL;
                                      	return 0;
                                      }
                                      

                                      柔性数组的结构:在这里插入图片描述

                                        既然这块空间是 m a l l o c malloc malloc 出来的,也就是说他可以通过 r e a l l o c realloc realloc 来调整大小,所以这个数组可变长变短,不就是柔性吗

                                        

                                        

                                      8.3、柔性数组的优势

                                        

                                        上述 t y p e type type_ a a a 结构,也可以设计为下面的结构,也能完成同样的效果

                                      #include
                                      #include
                                      typedef struct st_type
                                      {
                                      	int i;
                                      	int* p_a;
                                      }st_type;
                                      int main()
                                      {
                                      	st_type* p = (st_type*)malloc(sizeof(st_type));
                                      	p->i = 100;
                                      	p->p_a = (int*)malloc(p->i * sizeof(int));
                                      	///业务处理
                                      	for (i = 0; i p_a[i] = i;
                                      	}
                                      	//释放空间
                                      	free(p->p_a);
                                      	p->p_a = NULL;
                                      	free(p);
                                      	p = NULL;
                                      	return 0;
                                      }
                                      

                                      图示:

                                      在这里插入图片描述

                                        上述两个方法都可以达到类似的效果

                                        但是使用柔性数组有两个好处:

                                        

                                      • 方便内存释放

                                          如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把结构体返回给用户。用户调用 f r e e free free 可以释放结构体,但是用户并不知道结构体内的成员也需要 f r e e free free,所以你不能指望用户来发现这个事。

                                          所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次 f r e e free free 就可以把所有的内存释放掉

                                          

                                      • 有利于访问速度

                                          连续的内存有益于提高访问速度,也有益于减少内存碎片4

                                          

                                          

                                        九、C/C++中内存区域划分

                                        在这里插入图片描述

                                        C/C++程序内存分配的几个区域

                                        • 内核空间:操作系统核心(内核)运行的地方,在这个区域,操作系统可以直接访问硬件,并执行特权指令。我们用户是无权访问这块空间的
                                        • 栈区:在执行函数时,函数内局部变量的存储单元都是在栈上创建,函数执行结束时这些存储单元自动释放。栈内存分配运算内置于处理器的指令中,效率很高,但是分配的内存容量有限。栈区只要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
                                        • 堆区:堆区一般是用来存储程序运行期间动态分配内存的地方。堆区的内存分配是在程序运行时动态进行的,程序员可以通过调用标准库函数(如 m a l l o c malloc malloc、 c a l l o c calloc calloc、 r e a l l o c realloc realloc等)来在堆区中分配内存,并在不需要时手动释放这些内存(使用 f r e e free free函数)。使用堆区需要注意内存泄漏( m e m o r y l e a k memory leak memoryleak)的问题,即程序在不再需要某块内存时没有释放它,导致程序占用的内存越来越多。
                                        • 数据段:数据段也叫做静态区,主要用来存放全局变量、静态数据、全局变量。程序结束后由系统释放
                                        • 代码段:存放函数体(类成员函数和全局函数)的二进制代码、只读常量(字符串常量)
                                          1. 内存开辟失败:动态内存开辟失败的原因一般都是所空间太大,没有足够的空间 ↩︎

                                          2. p e r r o r perror perror函数:有关该函数的具体介绍请看:《【C语言】——字符串函数的使用与模拟实现(下)》 ↩︎

                                          3. 返回值为 1: m a i n main main 函数程序正常退出,返回值为 0;异常退出,返回值为 1 ↩︎

                                          4. 我们开辟内存时,不会紧接着上一块内存开辟,而会留下一点空隙,开辟次数越多,留下的空隙也就也多,这些空隙称为内存碎片。 ↩︎

微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon