动态内存管理:吃透 malloc/calloc/realloc/free,动态内存 + 柔性数组 + 经典错题一次懂

我走的并不快,但会一直走,会一直加油

文章目录

动态内存管理

前提知识

1.头文件

  • 本章提到的"free,malloc,calloc,realloc"都是在"stdlib.h"这个头文件中

2.传空指针NULL/野指针

  • 所有标准 C 的字符 / 字符串 / 内存函数,传入++空指针NULL/野指针++,行为都是「未定义」,程序大概率直接崩溃

    • free(NULL) 合法
    • realloc(NULL, size) 等价于malloc(size),但不等价于calloc(calloc 会初始化 0,realloc 不会)
  • 空指针解引用非法

3.判空操作

  • perror的用法:

    • 头文件:只需要stdio.h
    • 使用时在后面加上括号和一个双引号字符串,如果出错,会自动打印出"字符串+冒号+错误信息"
      • 比如:perror("错误是")
      • perror("malloc")------在malloc后面用,意为"malloc的错误是xxx "
  • 判空操作

    • 在使用malloc或者calloc或realloc后,都要对得到的指针进行判空,标准如下:

    cpp 复制代码
    if(p == NULL)
    {
        perror("malloc");//假设是通过malloc创建了指针p对应的内存
        return 1;//返回1代表程序异常失败
    }
    else
    {
        //进行操作
    }



动态内存管理

(1)free

1.使用方式

  • 必须在「最后一次使用这块内存之后」,「指针失效之前」调用

  • free(指针)

    • 其中的指针是malloc,calloc或realloc这些动态内存函数申请的空间对应的首地址
      • 如果申请失败 就没有内存,不需要释放
    • 可以传NULL
  • 在使用完free后,对应的指针变成野指针这时候要将该指针置为NULL

    cpp 复制代码
    free(p);
    p=NULL;

(2)malloc

1.英文

  • memory allocate:内存分配

2.使用方式

  • void* malloc (size_t size);

    • 后面括号直接加上想创建的字节长度
    • 返回值是泛型指针,如果想赋给其他类型的指针,在赋值之前需要强制类型转换
  • 使用完成后需判断是否使用成功,成功了就继续,没成功(空指针NULL)就退出程序(return 1)

  • 示例1:

    cpp 复制代码
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int* pa = (int*)malloc(40);//直接申请40个字节的空间
    	if (pa == NULL)
    	{
    		perror("malloc");
    		return 1;
    	}
    	else
    	{
    		for (int i = 0; i < 10; i++)
    		{
    			pa[i] = i;//pa是地址,相当于数组名,pa[i]相当于" *(pa+i)"
    		}
    	}
    	for (int i = 0; i < 10; i++)
    	{
    		printf("%d ", pa[i]);//pa是地址,相当于数组名,pa[i]相当于" *(pa+i)"
    	}
    	printf("\n");
    
    	free(pa);
    	pa = NULL;
    
    
    	return 0;
    }
  • 示例2:

    使用malloc函数模拟开辟一个3*5的整型二维数组,开辟好后,使用二维数组的下标访问形式,访问空间

    不要忘了判空和最后 释放内存


(3)calloc

1.英文

  • contiguous allocate:连续分配
  • clear allocate:清零分配

2.和malloc的差别

  • 传参改变:void* calloc (size_t num, size_t size);

    变成了两个参数:

    • 想创建几个:num
    • 每个几个字节:size
  • 创建的指针对应的内存会初始化为全0


(4)realloc

1.英文

  • reallocate:重新分配内存

2.注意事项

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

    • 也是泛型指针,需要在前面强制类型转换
    • realloc传参,第一个参数是地址,第二个参数是想要改变成的大小(相当于malloc的参数)
  • realloc和前面提到的calloc和malloc一样,都可能会出现错误,所以使用之前要先判空:

3.两种功能

  • 改变malloc或calloc创建的地址对应的内存空间的大小

    • (假设通过malloc/calloc创建的指针为a)

    • 使用realloc时,先初始化一个新的指针(避免原指针丢失),

      • 然后对新的指针判空,如果是空指针,就perror报个错并且return 1,在return之前,还要free(a),这个可千万别忘了,忘了会内存泄漏
    • 如果生成成功,就将tmp传给a

    cpp 复制代码
    #include<stdio.h>
    #include<stdlib.h>
    //先使用calloc创建一个能存5个int的内存,然后放进去12345,然后通过realloc修改成能放10字节,然后再放6到10
    int main()
    {
    	int* a = (int*)calloc(5 ,sizeof(int));
    	//判断是否是空指针
    	if (a == NULL)
    	{
    		perror("calloc");
    		return 1;
    	}
    	else
    	{
    		//先打印看看是不是初始化为全0
    		printf(" 先打印看看是不是初始化为全0:\n");
    		for (int i = 0; i < 5; i++)
    		{
    			printf("%d ", a[i]);
    		}
    		printf("\n");
    		
    		printf(" 将5个数字放进去:\n");
    		for (int i = 0; i < 5; i++)
    		{
    			a[i] = i + 1;
    			printf("%d ", a[i]);
    		}
    		printf("\n");
    
    		//使用realloc,将长度变成十个字节
    		int* tmp = (int*)realloc(a, 10 * sizeof(int));
    		//判断是否是空指针
    		if (tmp == NULL)
    		{
    			perror("realloc");
    			free(a);//如果失败了,不要忘了把该释放和置空
    			return 1;
    		}
    		else
    		{
    			//这时候再将tmp的地址进行使用
    			a = tmp;
    			tmp = NULL;
    
    			printf("后面再放6到10:\n");
    
    			for (int i = 0; i < 5; i++)
    			{
    				a[i + 5] = i+6; 
    			}
    			for (int i = 0; i < 10; i++)
    			{ 
    				printf("%d ", a[i]);
    			} 
    		} 
    	} 
    	//千万不要忘了释放和置空
    	free(a);
    	a = NULL;
    
    	return 0;
    }
  • 实现和malloc/calloc相同的功能:生成新的动态内存

    • 只需要传参时,第一个参数是NULL即可

4.分类讨论

  1. realloc使用失败

    1. 返回NULL,原内存保持不变------使用tmp并判空
  2. 成功

    1. 情况一:返回原地址

    2. 情况二:返回新的起始地址,原地址的内存会被释放

      如何正确使用

      1. 使用tmp------int* tmp = (int*)realloc(a, 10 * sizeof(int));
      2. 成功后修改原指针变量的指向------a = tmp;
      3. 将tmp置为空------tmp = NULL;



常见的错误

(1)创建失败,对空指针解引用

  • 如果malloc/calloc/realloc创建失败会得到空指针NULL,对空指针解引用是非法现象
cpp 复制代码
int* p=(int*)malloc( INT_MAX );//INT_MAX是整形类型的最大值,p是个空指针
*p=3;//对空指针解引用

(2)对内存越界访问

cpp 复制代码
	int* p = (int*)malloc(2 * sizeof(int));
	for (int i = 0; i <= 2; i++)
	{
		p++;
		printf("%d ", p[i]);
	}

(3)free的对象不是动态开辟内存的首元素的指针

cpp 复制代码
	int* p = (int*)malloc(2 * sizeof(int));
	p++;
	free(p);

(4)对同一块动态开辟内存多次使用free

cpp 复制代码
	int* p = (int*)malloc(2 * sizeof(int)); 
	free(p);
	free(p);

(5)忘记释放动态开辟的内存

cpp 复制代码
	int* p = (int*)malloc(2 * sizeof(int));
	for (int i = 0; i <= 2; i++)
	{
		p++;
		printf("%d ", p[i]);
	} 
	return 0;

(6)使用free释放非动态开辟的内存

cpp 复制代码
int a=0;
int* p=&a;
free(p);



动态内存经典笔试题分析

(1)传值调用+没有free

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}
int main()
{
	Test();
	return 0;
}
  • 错误:

    1. p和str是两个指针变量,在GetMemory开始之时,二者本来相同,但是这个函数将p的值进行了改变,从而实现了malloc,但是和str没有关系
    2. 对p进行malloc后,没有free,导致"内存泄露"
  • 两种修改方案:

    1. GetMemory修改参数,改成传址调用,并且在Test中使用后要free并置空

      cpp 复制代码
      #include <stdio.h>
      #include <stdlib.h>
      #include<string.h>
      void GetMemory(char** p)
      {
      	*p = (char*)malloc(100);
      }
      void Test(void)
      {
      	char* str = NULL;
      	GetMemory(&str);
      	strcpy(str, "hello world");
      	printf(str);
          
          free(str);// 必须free,避免内存泄漏
          str=NULL; // 置空,防止野指针
      }
      int main()
      {
      	Test();
      	return 0;
      }
    2. GetMemory直接返回malloc创造的地址

      cpp 复制代码
      #include <stdio.h>
      #include <stdlib.h>
      #include<string.h>
      char* GetMemory( )
      {
      	return (char*)malloc(100);
      }
      void Test(void)
      {
      	char* str = GetMemory( );//没有参数,直接返回char*指针
      	strcpy(str, "hello world");
      	printf(str);
              
          free(str);// 必须free,避免内存泄漏
          str=NULL; // 置空,防止野指针
      }
      int main()
      {
      	Test();
      	return 0;
      }

(2)访问局部变量的指针

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}
  • char p[] = "hello world";只在函数中存在,是局部的,函数使用完后自动销毁,所以p从GetMemory传回去后已经是空指针NULL了,不能打印空指针

(3)没有free

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);//后面没有free
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
int main()
{
	Test();
	return 0;
}
  • p = (char)malloc(num);//后面没有free

    • 应该将test改为

      cpp 复制代码
      void Test(void)
      {
      	char* str = NULL;
      	GetMemory(&str, 100);
      	strcpy(str, "hello");
      	printf(str);
      
      	free(str);
      	str = NULL;
      }

(4)free完后成为野指针

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}
  • 野指针不是NULL,if语句执行,strcpy访问野指针,非法访问内存

  • 修改(free完了要置空):

    cpp 复制代码
    	free(str);
    	str=NULL;



柔性数组

1.是什么

  • 结构体中,末尾的一个"无长度"的数组,能用 malloc /calloc/realloc进行大小修改

    cpp 复制代码
    struct st_type
    {
        int i;
        int a[0];//柔性数组成员
        //如果报错,可以写成int a[]
    };

2.特点

  1. 柔性数组必须在结构体最后一位

  2. 前面必须有结构体成员

  3. 可以使用malloc/calloc/realloc分配内存

    1. 分配的内存必须大于不算上柔性数组的大小
  4. sizeof算不到柔性数组的大小

    1. 我觉得这是为了在对柔性数组进行大小创建的时候更方便,如果我想直接让柔性数组大小为3个int,可以直接写成:struct st_type* tmp = (struct st_type*) malloc( sizeof(s)+3*sizeof(int) );

3.优点

为啥不在结构体中直接放一个数组(的首元素地址)呢?

  • 两种方式的比较:

    1. 使用柔性数组

      cpp 复制代码
      struct st_type
      {
      	int i;
      	int a[];//柔性数组成员
      } ;
      
      int main()
      {
      	 ///直接通过柔性数组的方式进行创建 柔性数组有100个int
      	///将结构体中的i设为100
      		///将柔性数组遍历赋值 
      	struct st_type* s = (struct st_type*)malloc(sizeof(struct st_type) + 100 * sizeof(int));
      	s->i = 100;
      
      	for (int j = 0; j < 100; j++)
      	{
      		(s->a)[j] = j;///s->a:拿到柔性数组a的首地址 ;  (s->a)[j]:访问柔性数组a中的元素
      	}
      
      	free(s);
      	s = NULL;
      	
      	return 0;
      }
    2. 结构体中放数组(首元素地址)

      *cpp 复制代码
      struct st_type
      {
      	int i;
      	int *a;///通过malloc直接创建数组
      } ;
      
      int main()
      {
      	struct st_type* ps = (struct st_type*)malloc( sizeof(struct st_type) );
      
      	ps->i = 100;
      	ps->a = (int*)malloc(100 * sizeof(int));
      
      	for (int j = 0; j < 100; j++)
      	{
      		ps->a[j] = j;
      	}
      
      	free(ps->a);
      	ps->a = NULL;
      	free(ps);
      	ps = NULL;
      
      	return 0;
      }
  • 优点:
    • 做⼀次free就可以把所有的内存也给释放掉,放心
    • 有益于提⾼访问速度,也有益于减少内存碎⽚。



C/C++中程序内存区域划分

  • 栈区**(stack)**:局部变量,函数参数、返回数据、返回地址等

    • 效率高
    • 容量有限
  • 堆区(heap): 存放通过 malloc() 等动态分配的内存

    • ⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)

    回收 。分配⽅式类似于链表。

  • 数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。

  • 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码

相关推荐
炘爚17 天前
C++(手写Mystring|柔性数组、引用计数与写时拷贝的核心用法)
柔性数组
星轨初途21 天前
【C/C++底层修炼】拆解动态内存管理:四大动态内存函数、六大错误与柔性数组
c语言·开发语言·c++·经验分享·笔记·柔性数组
01二进制代码漫游日记1 个月前
C语言:柔性数组
柔性数组
我能坚持多久3 个月前
D19—C语言动态内存管理全解:从malloc到柔性数组
c语言·开发语言·柔性数组
我是大咖3 个月前
关于柔性数组的理解
数据结构·算法·柔性数组
Allen_LVyingbo3 个月前
面向“病历生成 + CDI/ICD”多智能体系统的选型策略与落地实践(三)
算法·自然语言处理·性能优化·知识图谱·健康医疗·柔性数组
栈与堆3 个月前
数据结构篇(1) - 5000字细嗦什么是数组!!!
java·开发语言·数据结构·python·算法·leetcode·柔性数组
yuanmenghao3 个月前
自动驾驶中间件iceoryx - 内存与 Chunk 管理(一)
c++·vscode·算法·链表·中间件·自动驾驶·柔性数组
山上三树4 个月前
柔性数组(C语言)
c语言·开发语言·柔性数组