• 动态内存精讲


    前言

    这便文章是知识型🧣🧣🧣的文章,小编认为不仅要做到知识的分享还要给枯燥的知识内容🧩🧩🧩,加上一些有趣的东西🎉🎉🎉
    如果您认可小编的努力,希望得到您的一个赞🙏🙏🙏
    flag:键盘敲烂,月薪过万。东方不败,唯我独尊👍👍👍

    • 首先开始之前,欣赏一张图片
      请添加图片描述
      当然不是这张了,是下面这张
      在这里插入图片描述
    • 心之所向,素履以往;生如逆旅,一苇以航。

    动态内存开辟

    在这里插入图片描述

    首先学四个动态内存函数

    是不是回到初中的感觉,或者高中,一入编程深似海,函数从此成堆来。😭😭

    一对亲兄弟–malloc和free

    大哥:malloc

    void * malloc(size_t size);
    
    • 1
    人物简介
    malloc向内存申请一块连续可用的空间,并返回指向这块空间的指针
    战斗力开辟成功——返回一个指向开辟好的空间的地址
    开辟失败——返回一个NULL【啥也没有,啥也不是】
    特点返回的类型是void* ,malloc函数不知道开辟空间的类型,在具体使用的时候自己决定【就好像熊孩子自己干了坏事,自己都事情的严重型,只有发现时才能定性后果的严重】

    当然不要忘记了熊孩子的父母——头文件

    #include
    
    • 1
    • 大哥武艺展示
      在这里插入图片描述
    #include
    #include
    int main(){
    	//动态开辟内存
    	int* p=(int*)malloc(10*sizeof(int));//也可以写成数字但是不推荐
    	if(p==NULL)
    	{
    		perror("main");
    		return 0;
    	}
    	for(int i=0;i,10;i++)
    	{
    		//结果为随机值
    		printf("%d\n",p[i]);//p[i]==*(p+i)
    	}
    	free(p);//回收空间
    	p=NULL;//手动设置p为NULL
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    如果开辟的空间过大会开辟失败:mian(Not enough space)

    四弟:free

    • 为什么时四弟?😕😕,请继续阅读
    • 四弟真身
    void fre(void * ptr);
    
    • 1
    人物简介
    free用来释放动态开辟的内存
    异常如果ptr指向的空间不是动态开辟的,free函数的行为是未定义的
    如果ptr是NULL指针,则函数什么都不做

    calloc二弟

    void * calloc(size_t num,size_t size);
    
    • 1

    Allocates an array in mamory with elements initialized to 0
    翻译的意思就是:会初始化内存中的数组为0

    • 二弟秀时刻
      在这里插入图片描述
    #include
    #include
    int main(){
    	int* p=(int* )calloc(10,sizeof(int));
    	if(p==NULL)
    	{
    		perror("main");
    		return 1;
    	}
    	for(int i=0;i<10;i++)
    	{
    		printf("%d",p[i]);	
    	}
    	free(p);
    	p=NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    realloc小三【最强挂王】

    void* reallic(void* ptr,size_t size);
    
    • 1

    ptr——要调整的内存大小
    size—— 调整之后新大小
    返回值是调整之后的内存的起始位置
    在调整原内存空间的基础上,还会将原来内存中的数据 移动到新的空间

    • 挂王简介:
      • 灵活管理动态内存
      • 可以对申请的内存大小进行动态的调整
    • 可能的情况
      在这里插入图片描述
    • 跟大哥单挑–动态开辟功能
    int main()
    {
    	//类似于malloc,直接在堆区开辟内存
    	int *p=(int* )realloc(NULL,10*sizeof(int));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    常见的动态内存(bug)错误

    1 对NULL的接引用操作

    void test()
    {
    	int*p=(int*)malloc(INT_MAX/4);
    	*p=20;//如果开配失败,p是空指针,就会有问题
    	free(p);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2 对动态内存开辟空间的越界访问

    #include
    int main()
    {
    	int *p=(int *)malloc(10*sizeof(int));
    	if(p==NULL)
    		return 1;
    	for(int i=0;i<20;i++)
    	{
    		p[i]=i;//i>9之后是越界的
    	}
    	free(p);
    	p=NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3 使用free释放非动态内存开辟的空间

    #include
    int main() {
    	int arr[10]={0};//栈区
    	int * p=arr;
    	free(p);//使用free释放非动态内存开辟的空间
    	p=NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4 使用free释放动态内存的一部分

    void test()
    {
    	int* p=(int *)malloc(100);
    	p++;
    	free(p);//p不在指向动态内存的起始位置
    	
    	int *p=(int *)malloc(10* sizeof(int));
    	if(p=NULL)
    		return 1;
    	for(int i=0:i<2;i++)
    	{
    		*p++=i;
    	}
    	free(p);
    	p=NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5 对同一块动态开辟的空间,多次释放

    int main()
    {
    	int*p=(int*)malloc(10);
    	free(p);
    	free(p);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 解决方法一:将第二次的释放删去
      但是有时候代码太长,很复杂,不想搞了
      请添加图片描述
    • 解决方法二:p置0
    int main()
    {
    	int*p=(int*)malloc(10);
    	free(p);
    	p=NULL;
    	free(p);//在第一次释放p之后,使p指向NULL;即使再次释放,free(NULL)不做任何操作
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6 动态开辟的空间忘记释放——内存泄漏

    void test()
    {
    	//p是在栈区
    	//malloc()在堆区开辟
    	int *p=(int*)malloc(100);
    	if(p!=NULL)
    	{
    		*p=20;
    	}
    	//未释放内存
    }
    
    int main() {
    	test();
    	while(1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这个我来讲一个故事:🎉🎉🎉
    p警长安排malloc警员去犯罪集团中做卧底。【text函数中动态开辟内存】
    只有他们两个人知道这件事,两人保持单线联系。一年后p警长不幸牺牲【text函数执行完成,释放栈区内存的内容】
    警局再也找不到malloc了,但是还要给malloc薪水。【无法释放堆区内存,但程序不结束,仍然占用内存】

    • 动态开辟的空间,2种回收的方式
        1. 主动free
      • 程序结束,自动释放

    相关错误练习

    在这里插入图片描述

    错误1:内存泄漏

    #include
    #include
    #include
    void GetMemory(char* p)
    {
    	p = (char*)malloc(100);
    }
    void Test(void)
    {
    	char* str = NULL;
    	GetMemory(str);
    	strcpy(str,"hello world");
    	printf(str);
    	free(str);
    	str = NULL;
    }
    int main()
    {
    	Test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    解决方法一:返回p指针

    #define _CRT_SECURE_NO_WARNINGS
    #include
    #include
    #include
    char* GetMemory(char* p)
    {
    	p = (char*)malloc(100);
    	return p;
    }
    void Test()
    {
    	char* str = NULL;
    	str=GetMemory(str);
    	strcpy(str,"hello world");
    	printf(str);
    	free(str);
    	str = NULL;
    }
    int main()
    {
    	Test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    解决方法二:地址传递

    • 接收指针的地址,p是二级指针
    • *p:对二级指针p解引用得到一级指针str的地址,使str指向堆区
    #define _CRT_SECURE_NO_WARNINGS
    #include
    #include
    #include
    void GetMemory(char** p)
    {
    	*p = (char*)malloc(100);
    	return p;
    }
    void Test()
    {
    	char* str = NULL;
    	GetMemory(&str);
    	strcpy(str,"hello world");
    	printf(str);
    	free(str);
    	str = NULL;
    }
    int main()
    {
    	Test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    错误二:非法访问

    char* GetMemory()
    {
    	char p[]="Hello world";
    	return p;
    }
    void Test()
    {
    	char* str=NULL;
    	str=GetMemory();
    	printf(str);
    }
    int main()
    {
    	Test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • GetMemory函数内部创建的数组是在栈区创建的
    • 出了函数,p数组的空间就还给了操作系统
    • 返回的地址是没有实际的意思,如果通过返回的地址,去访问内存就是非法访问

    错误3:野指针

    int* f()
    {
    	int * ptr;//野指针未初始化
    	*ptr=10;
    	return ptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    错误4:未释放

    void Getmemory(char** p,int num)
    {
    	*p=(char*)malloc(num);
    }
    vloid Test()
    {
    	char* str=NULL;
    	GetMemory(&str,100);
    	strcpy(str,"Hello");
    	printf(str);
    	//添加的解决释放代码
    	free(p);
    	p=NULL;
    }
    int main()
    {
    	Test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    错误5:释放后的非法访问

    void Test()
    {
    	char* str=(char*)malloc(100);
    	strcpy(str,"hi");
    	free(str);//释放了堆区的内存,但是str中还保存着地址,但是使用的权限归操作系统,没权访问
    	if(str!=NULL)
    	{
    		strcpy(str,"hello");//错误,非法访问
    		printf(str);
    	}
    }
    int main()
    {
    	Test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    • C/C++程序内存分配的几个区域:
    1. 栈区(stack):在执函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据返回地址等
    2. 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表
    3. 数据段(静态区static)存放全局变量、静态数据。程序结束后由系统释放
    4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码

    static

    • 普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
    • 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁
    • 生命周期变长

    柔性数组

    • 结构体中最后一个元素是未知大小的数组,叫柔性数组。
      两种形式:
    typedef struct st_type
    {
    	int i;
    	int a[0];//柔性数组成员
    } type_a;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    typedef struct st_type
    {
    	int i;
    	int a[];//柔性数组成员
    } type_a;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    举例:柔性数组

    struct s
    {
    	int n;
    	int arr[0];//大小未知
    }
    int main()
    {
    	//期盼arr的大小是10个整数
    	struct S* ps=(struct S*)malloc(sizeof(struct S)+10*sizeof(int));
    	ps->n=10;
    	for(int i=0;i<10;i++)
    	{
    		ps->arr[i]=i;
    	}
    	//增加
    	struct S* ptr=(struct S*)realloc(ps,sizeof(struct S)+20*sizeof(int));
    	if(ptr!=NULL)
    	{
    		ps=ptr;
    	}
    	//使用
    	free(ps);
    	ps=NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    同效果的非柔性数组的代码

    struct S
    {
    	int n;
    	int* arr;
    }
    int mian() 
    {
    	struct S* ps=(struct S*)malloc(sizeof(struct S));
    	if(ps==NULL)
    		reurn 1;
    	ps->n=10;
    	ps->arr=(int*)malloc(10*sizeof(int));
    	if(ps->arr==NULL)
    	{
    		return 1;
    	}
    	for(int i=0;i<10;i++)
    	{
    		ps->arr[i]=i;
    	}
    	//增加
    	int* ptr=relloc(ps->arr,20*sizeof(int));
    	if(ptr!=NULL)
    	{
    		ps->arr=ptr;
    	}
    	free(ps->arr);
    	ps->arr=NULL;
    	free(ps);
    	ps=NULL;
    	reuturn 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    对比:

    1. 柔性数组需要释放的指针少,不容易出错
    2. 频繁地申请空间,增加了内存碎片,降低了效率,降低了内存占有率

    在这里插入图片描述

    • 第一个好处是:方便内存释放
      如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
    • 第二个好处是:这样有利于访问速度
      连续的内存有益于提高访问速度,也有益于减少内存碎片

    不相关的知识:define与typedef的区别

    #define INT_PTR int*
    typedef int* int_ptr;
    INT_PTR a,b;//int* a,b==int*a,(int)b;
    int_ptr c,d//int*c,d==int*c,*d;
    
    • 1
    • 2
    • 3
    • 4

    问题:那个变量不是指针?
    答案:b

    • define只有替换的作用:
    • typedef是定义了一种数据类型:
  • 相关阅读:
    如何准备一场Java面试?
    java毕业设计普通中学体育卫生信息管理系统源码+lw文档+mybatis+系统+mysql数据库+调试
    一遍关于vue基础语法下篇
    第 4 篇:绘制四边形
    Nacos 安装与部署
    会议OA项目之我的审批&&签字功能
    降低打新的预期
    第二十二课,实例化(instancing)
    盘点 | 云原生峰会重磅发布
    Maven下的依赖管理
  • 原文地址:https://blog.csdn.net/yang2330648064/article/details/125886216