当前位置: 首页 > news >正文

动态内存管理柔性数组

动态内存管理

我们熟知的内存开辟:

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

但其有个缺点就是开辟大小固定;数组空间一旦确定了就不能调整。
这就不得不提到动态内存的开辟了

1.动态内存的开辟和释放:malloc和free

都需要包含头文件<stdlib.h>

malloc

void* malloc(size_t size);

功能:向内存的栈区申请一块连续可用的空间,并返回指向这块空间的起始地址
在这里插入图片描述

开辟成功,返回这块空间起始地址;开辟失败,返回一个NULL指针,所以其返回值需认真检查 。
开辟示例:

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(5 * sizeof(int));//申请20个字节的空间,存5个整数//注意有时候开辟的空间过大可能会开辟失败if (p == NULL){perror("malloc");//打印错误信息return 1;}//开辟成功//使用这块空间int i = 0;for (i = 0;i < 5;i++){*(p + i) = i + 1;//p[i]=i+1;}for (i = 0;i < 5;i++){printf("%d ", p[i]);}return 0;
}

在这里插入图片描述

free

当开辟的内存使用完后需要还回去,这就涉及到了动态内存的释放和回收,这里有一个专门的函数——free

void free(void* ptr);
  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
  • 如果参数ptr是NULL指针,则函数什么事都不用做
    同malloc所例代码,使用free还空间
    在这里插入图片描述

2.动态内存分配及扩容:calloc和realloc

calloc

calloc 是动态内存分配的

void* calloc(size_t num,size_t size);
  • 函数功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
  • 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节全部初始化为0
    例:
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10 * sizeof(int));if (p == NULL){perror("calloc");return 1;}int i = 0;for (i = 0;i < 10;i++){printf("%d\n", p[i]);}free(p);p = NULL;return 0;
}

在这里插入图片描述

realloc

动态内存开辟好了有时需要对其大小进行调整,那么realloc函数就可实现

void* realloc(void* ptr,size_t size);
  • ptr是要调整的内存地址;size调整之后新大小,单位是字节;返回值为调整之后的内存起始位置。
    这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

  • realloc在调整内存空间时存在两种情况:
    (1)原有空间之后有足够大的空间
    (继续在后面开辟空间去使用)
    (2)原有空间之后没有足够大的空间
    (将旧的空间的数据,拷贝一份到新的空间;返回新的空间的起始地址;释放掉旧的地址)

  • 使用:

#include<stdio.h>
int main()
{//申请一块空间,用来存放1—5的数字int* p = (int*)malloc(5 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//使用内存空间int i = 0;for (i = 0;i < 5;i++){p[i] = i + 1;}//增容int*p2=realloc(p, 10 * sizeof(int));if (p2 == NULL){perror("realloc");free(p);p = NULL;return 1;}p = p2;//继续使用p来维护新的空间for (i = 5;i < 10;i++){p[i] = i + 1;}free(p);p = NULL;return 0;
}

当然,realloc也可以用来开辟空间,如下:

int main()
{int* p = (int*)realloc(NULL, 40);//等价于malloc(40)//...return 0;
}

3.常见的动态内存的错误

(1).对NULL指针的解引用操作

产生原因:没有对malloc,realloc,calloc函数的返回值做判断。

int main()
{int*p=(int*)malloc(INT_MAX);int i=0;for(i=0;i<10;i++){p[i]=i+1;//这里可能因为p为空指针而出错//需要再循环前加上:/*if(p==NULL){perror("malloc");return 1;}*/}free(p);p=NULL;return 0;
}

(2).对动态开辟空间的越界访问

int main()
{int*p=(int*)malloc(10*sizeof(int));if(p==NULL){perror("malloc");return 1;}int i=0;for(i=0;i<=10;i++){p[i]=i+1;//当i等于10的时候越界访问}free(p);p=NULL;return 0;
}

(3).对非动态开辟内存使用free访问

int main()
{int arr[10]={1,2,3,4,5,6,7,8,9};//栈区上的数组int* p=arr;//使用free(p);//手动回收的空间也需要手动开辟,不能回收非动态开辟内存p=NULL;return 0;}//当程序退出main函数时会自动回收

这会导致程序未响应

(4).使用free释放一块动态内存的一部分

int main()
{int*p=(int*)malloc(10*sizeof(int));if(p==NULL){perror("malloc");return 1;}int i=0;for(i=0;i<5;i++){*p=i+1;p++;//这样写会出现问题,p不再指向gakongjian如下图//这样写是没问题的://*(p+i)=i+1;}free(p);//当free释放空间的时候,一定要给该空间的起始位置p=NULL;return 0;
}

在这里插入图片描述

(5).对同一块动态内存多次释放

int main()
{int*p=(int*)mallloc(10*sizeof(int));if(p=NULL){perror("malloc");return 1;}//使用free(p);//为避免错误,free释放后要及时将p置为空//即p=NULL;//...free(p);//二次释放了p=NULL;return 0;
}

这会导致程序未响应

(6).动态内存忘记释放(内存泄漏)

忘记手动使用free进行动态内存释放会导致内存泄漏。
应做到谁申请的空间谁释放;不使用空间及时释放;自己(函数1)不方便释放的空间,要告诉别人(函数2)释放

4.动态内存经典例题

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)//正确方法:(char** p)
{p = (char*)malloc(100);//改正:*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);//改正:GetMemory(&str);strcpy(str, "hello world");printf(str);/*释放:free(str);str=NULL;*/
}
int main()
{Test();return 0;
}//存在对NULL的解引用操作,程序崩溃

程序崩溃原因:

  • 当str传递给GetMemory函数的时候,采用的是值传递,形参变量p只是str的一份拷贝。当把malloc申请的空间的起始地址存放在p中时不会修改str,str依然为NULL。因而当GetMemory函数返回后,再去调用strcpy函数需要将"hello world"拷贝到str指向的空间时,程序崩溃;
  • 且malloc申请的空间没有手动去释放,存在内存泄露
//返回栈空间地址的问题
char* GetMemory(void)
{char p[] = "hello world";return p;//p是一个局部变量,是栈区上的数组,出了作用域这块空间就销毁
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}//打印出随机值
//正确改法:static char p[] = "hello world";
//加入static该数组就存在内存的静态区了,退出后不会销毁
//内存泄漏问题
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello world");printf(str);//释放:free(str);//str=NULL;
}
int main()
{Test();return 0;
}
//未使用free去释放malloc开辟的内存,内存泄露了
//野指针问题
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);//内存提前释放了,str称为野指针if (str != NULL){strcpy(str, "world");//这里非法访问内存printf(str);}
}
int main()
{Test();return 0;
}//程序崩溃,什么都打印不出来
//正确做法,在释放后把指针置为空
// 即str=NULL;

柔性数组

  1. C99中,结构中的最后一个元素允许是位置大小的数组,这就叫【柔性数组】成员。例如:
struct st
{int i;int a[0];//大小未知,即柔性数组成员//或者写成:int a[];
}

柔性数组的特点

  • 结构中的柔性数组成员前面必须至少有一个其他成员

  • sizeof返回的这种结构大小不包括柔性数组的内存
    在这里插入图片描述

  • 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小,如:

#include<stdio.h>
#include<stdlib.h>
struct S
{int n;int arr[];//柔性数组成员
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){perror("malloc");return 1;}//使用空间ps->n = 100;int i = 0;for (i = 0;i < 10;i++){ps->arr[i] = i + 1;}
//使用柔性数组,用malloc创建后,可随时调整空间//调整空间struct S* tmp=realloc(ps,sizeof(struct S)+20*sizeof(int));if(tmp==NULL){perror("realloc");return 1;}ps=tmp;//释放空间free(ps);ps = NULL;return 0;
}

当然,我们也可以自己写一个类似柔性数组的程序,用结构体包含一个指针的做法,代码如下:

struct S
{int n;int* arr;
};
int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){perror("malloc");return 1;}ps->n = 100;int* ptr = (int*)malloc(10 * sizeof(int));if (ptr == NULL){perror("malloc2");return 1;}ps ->arr=ptr;int i = 0;for (i = 0;i < 10;i++){ps->arr[i] = i + 1;}//当然也可以调整空间ptr=realloc(ps->arr, 20 * sizeof(int));if (ptr == NULL){perror("realloc");return 1;}ps->arr = ptr;free(ps->arr);free(ps);ps = NULL;return 0;
}

对比一下,柔性数组只需用malloc申请一次空间就满足结构体内存的使用,而我们自己写的则需要两次malloc进行内存创建。我们要知道,malloc用得越多,内存碎片就越多,浪费的也就越多。这样比较下来,柔性数组的优点是不是更显著一点了。

使用柔性数组的优点有:

  • 方便内存释放
  • 有利于提升访问速度
本次内容到这里就结束了,谢谢观看!
http://www.xdnf.cn/news/1459099.html

相关文章:

  • Vue 中绑定样式的几种方式
  • Process Explorer 学习笔记(第三章3.1.1):度量 CPU 的使用情况详解
  • 【Unity知识分享】Unity接入dll调用Window系统接口
  • 无限时长视频生成新突破!复旦联合微软、腾讯混元推出StableAvatar,仅需1张照片+1段音频实现真人说话视频
  • hutool的EnumUtil工具类实践【持续更新】
  • 揭秘23种设计模式的艺术与技巧之行为型
  • 美联储计划召开稳定币和代币化创新会议
  • 大数据框架Doris全面解析
  • 期权平仓后权利金去哪了?
  • 基于STM32的智能家居语音控制系统设计
  • Pycharm终端pip install的包都在C:\Users\\AppData\Roaming\Python\解决办法
  • 手写Spring框架
  • 前端跨域终极指南:3 种优雅解决方案 + 可运行 Demo
  • 解密注意力机制:为何它能在Transformer中实现高效并行计算?
  • STM32G4 速度环开环,电流环闭环 IF模式建模
  • 如何在Linux上部署1Panel面板并远程访问内网Web端管理界面
  • Kafka 开启 SASL_PLAINTEXT 双监听器认证(内网/外网)
  • 如何减少文档冗余和重复劳动
  • vite_react 插件 find_code 最终版本
  • MVCC是如何工作的?
  • bash自带的切片操作
  • 解锁“桐果云”的全链路能力矩阵,技术人必看的企业级数据应用方案
  • 阿里云轻量应用服务器部署WordPress与配置SSL 证书
  • 英飞凌ASIL-D级无刷电机驱动芯片TLE9189守护汽车安全
  • 第三方网站测试:WEB安全测试中DOM型XSS漏洞的检测
  • [Windows] PDF工具箱 PDF24 Creator 11.28.0
  • 为什么ApiFox的分页查询的返回Vo的数据没有全部展示? 只展示了返回有数据的?没有数据的为什么不展示?
  • N个代码片段之封装继承与多态
  • Docker(①安装)
  • 视频小浮窗Pip的实现