动态内存管理

简介: 动态内存管理

目录

前言

为什么存在动态内存分配

动态内存函数介绍

malloc

free

calloc

realloc

常见的动态内存错误

对NULL指针的解引用操作

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

对非动态开辟内存使用free释放

使用free释放一块动态开辟内存的一部分

对同一块动态内存多次释放

        动态开辟内存忘记释放(内存泄漏)

柔性数组

柔性数组特点

柔性数组的优点

总结


前言

在我们C语言中,我们知道,通常我们开辟一块空间用于存放变量的时候,这块空间是不会改变大小的,但是有的时候,需要我们程序运行起来才会知道我们需要多大的内存空间,那这种情况下,我们只能创建一个很大的数组,用来存放,这个时候就出现了不必要的浪费,那这个时候,就出现了我们的动态内存。

为什么存在动态内存分配

我们一般开辟内存:

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

image.gif

但是上述的开辟空间的方式有两个特点:

1. 空间开辟大小是固定的

2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

  但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。


 

动态内存函数介绍

malloc

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

如果开辟成功,则返回一个指向开辟好空间的指针。

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己

来决定。

如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。


如果我们想用malloc申请一块20个字节的空间给我们char类型的数组str用,那怎么用呢?

int main()
{
    char* str=NULL;
    str=(char*)malloc(20);
    if(str==NULL)
    {
        perror(str);
    }
    return 0;
}

image.gif

那这样就开辟了一块20个字节的空间给我们的str使用。

但是这样却会有一个致命的问题,这时候我们就要学习以下这个函数。

free

C语言提供了另外一个函数free

专门是用来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);

free函数用来释放动态开辟的内存。

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

如果参数 ptr 是NULL指针,则函数什么事都不做。

当我们用动态内存函数开辟空间之后,是需要进行内存释放的,不然就会造成内存泄漏,或者其他问题,这个时候,内存释放用的就是我们的free,它可以对我们开辟的空间进行释放,当我们进行释放之后,我们最好让先前接收这片空间的指针置空,以免造成野指针问题,那有了free之后,我们对于上面的例子代码进行升级。

free和malloc成对使用

int main()
{
    char* str=0;
    str=(char*)malloc(20);
    if(str==NULL)
    {
        perror(str);
    }
    free(str);
    str=NULL;
    return 0;
}

image.gif

calloc

void* calloc (size_t num, size_t size);

此函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。

与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

对于我们的malloc来说,可以申请任意字节的空间,比如17、21,那这些字节也可以强转给我们的int类型,只不过我们在往申请的这片空间中放东西的时候,因为我们的int类型是4个字节,所以放入到最后字节不够4个字节的空间时,就会产生截断,放不完全,而我们的calloc传的两个参数,第一个是要申请多少个元素,第二个是每个元素的大小,比如我们要为一个10个元素的int类型的数组申请空间,就可以像下面这样来申请:

int main()
{
    int* num=0;
    num=(int*)calloc(10,sizeof(int));
    if(num==NULL)
    {
        perror(num);
    }
    free(num);
    num=NULL;
    return 0;
}

image.gif

realloc

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

ptr 是要调整的内存地址

size 调整之后新大小

返回值为调整之后的内存起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

realloc函数的出现让动态内存管理更加灵活。

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时

候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小

的调整

int main()
{
    char* str=NULL;
    str=(char*)malloc(20);
    if(str==NULL)
    {
        perror(str);
    }
    char* p=NULL;
    p=(char*)realloc(str,10);
    if(p!=NULL)
    {
        str=p;
        p=NULL;
    }
    free(str);
    str=NULL;
    return 0;
}

image.gif

realloc在调整内存空间的是存在两种情况:

image.gif编辑

ps.

情况1:原有空间之后有足够大的空间。

当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2: 原有空间之后没有足够大的空间

当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小

的连续空间来使用。这样函数返回的是一个新的内存地址

常见的动态内存错误

对NULL指针的解引用操作

void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}

image.gif

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

 

void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}

image.gif

对非动态开辟内存使用free释放

void test()
{
int a = 10;
int *p = &a;
free(p);
}

image.gif

使用free释放一块动态开辟内存的一部分

 

void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}

image.gif

对同一块动态内存多次释放

 

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

image.gif

动态开辟内存忘记释放(内存泄漏)

 

void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}

image.gif

柔性数组

柔性数组,于C99标准中定义,它处于结构体当中,当我们结构体的最后一个成员是一个未知大小的数组的时候,就是我们的柔性数组,当然,这个结构体一定要有其他的成员,废话少说那我们直接上代码。

struct st_type
{
     int i;
     int a[0];//柔性数组
};

image.gif

这种写法可能在有的编译器上跑不过,那我们就换一种写法。

struct st_type
{
     int i;
     int a[];//柔性数组
};

image.gif

柔性数组特点

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

2>sizeof 返回的这种结构大小不包括柔性数组的内存。

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

例如:

typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

image.gif

柔性数组的优点

1>方便内存释放

当里面的成员不是柔性数组而是指针的时候,在使用的过程中,我们对其内部进行了二次的内存分配,而且把整个结构体的指针返回去了,别人使用这个结构体指针,他并不知道里面进行了二次的内存分配,他直接用free释放了整个结构体,不知道要对内部进行free,但是要是柔性数组,就没有这个问题,free一次就行了。

2>提高我们的访问速度

因为我们申请下来的空间是一片连续的空间,连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

总结

动态内存大大的提高了对内存空间的利用,避免了一些不必要的内存浪费。

好了本章的内容就到此为止了,希望各位佬多多支持!

相关文章
|
存储 安全 Java
基于springboot自习室预订管理系统
基于springboot自习室预订管理系统
|
8月前
|
人工智能 安全 Linux
《2024 龙蜥操作系统开源社区白皮书》正式发布 引领开源操作系统新征程
该白皮书详细阐述了龙蜥社区在技术创新、生态建设等多方面的成果与规划。
《2024 龙蜥操作系统开源社区白皮书》正式发布 引领开源操作系统新征程
|
11月前
|
Linux Shell 数据安全/隐私保护
Linux 初学者必学的 10 个命令,学习!
【10月更文挑战第28天】
219 1
Linux 初学者必学的 10 个命令,学习!
|
网络协议 程序员 UED
如何确保单聊消息100%送达?揭秘消息可靠传输的核心机制!
哈喽,大家好!我是技术好朋友小米,今天聊聊单聊消息的可靠传输。通过TCP的超时、重传、确认机制,结合去重和离线消息优化,我们可以设计出高效、可靠的消息传输系统。希望今天的分享能给大家带来帮助!如果有问题,欢迎留言交流。
218 0
如何确保单聊消息100%送达?揭秘消息可靠传输的核心机制!
|
移动开发 JavaScript 定位技术
百度地图开发:地图调起API(Web端)使用终点经纬度直接调用百度地图导航信息的解决方案
百度地图开发:地图调起API(Web端)使用终点经纬度直接调用百度地图导航信息的解决方案
663 0
|
开发者
阿里云2024年上云采购季活动云服务器优惠价格表参考
3月1日,阿里云正式上架了2024年的上云采购季活动,云服务器优惠价格表也随之出炉了,2024年的上云采购季活动中云服务器价格最低是2核2G配置,仅需99元1年,每天仅需0.27元;2核4G配置199元1年,每天只要0.54元。轻量应用服务器2核2G配置最低61元1年,2核4G配置最低165元1年。如果是选择月付的话,最低26.52元可购买一台4核16G配置,100G ESSD Entry云盘,10M带宽的经济型e实例云服务器。下面小编整理汇总的此次上云采购季活动用户可以购买到的具体云服务器配置及价格,以表格形式展示给大家,以供参考选择。
438 0
阿里云2024年上云采购季活动云服务器优惠价格表参考
|
网络协议 安全 网络安全
软考之详解计算机网络中的各种协议
软考之详解计算机网络中的各种协议
462 0
YI
|
SQL 存储 移动开发
数据库系统概论笔记(三)
数据库系统概论笔记(三)
YI
870 1
|
Web App开发 前端开发 JavaScript
Web前端快速开发 Bootstrap 响应式UI框架
Web前端快速开发 Bootstrap 响应式UI框架
769 0
Web前端快速开发 Bootstrap 响应式UI框架
|
机器学习/深度学习 人工智能 算法
【数据编制架构】Data Fabric 架构是实现数据管理和集成现代化的关键
【数据编制架构】Data Fabric 架构是实现数据管理和集成现代化的关键