前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >练习使用动态内存相关的4个函数:malloc、calloc、realloc、free

练习使用动态内存相关的4个函数:malloc、calloc、realloc、free

作者头像
用户11039545
发布2024-03-28 17:50:29
930
发布2024-03-28 17:50:29
举报
文章被收录于专栏:c语言c语言

在了解使用动态内存相关的四个函数之前,我们先了解一下,为什么要有动态内存分配?

首先,我们已经掌握了一种开辟内存的方式,就是直接使用int i=20;但是这样开辟空间有两个特点,1:空间开辟大小是固定的,2:数组在创建时,必须设定数组的长度,数组空间的大小一旦确定就不能更改

可以申请和释放空间,这样就⽐较灵活了

malloc

void* malloc (size_t size);

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

如果开辟成功,返回一个开辟成功的指针。

如果失败,则返回NULL。因此,在malloc使用之前必须检查是否为空指针。

返回类型为void*,因此,返回的类型由自己来决定。同时,size为0的时候,malloc的行为是标准未定义的,这取决于编译器。

free

free函数是专门用来做动态内存的释放和回收的。

void free (void* ptr);

free函数用来做动态内存的释放,如果ptr指向的空间不是动态内存,那么free的行为就是标准未定义的。如果ptr指向的空间为空,那么free什么事都不做。

malloc和free都声明在 stdlib.h 头⽂件中。

传递给free的是要释放空间的起始地址,例如下面的案例,传递的不是起始地址,就无法释放:

在free之后一定要置为空,否则会造成一些意想不到的灾难。

calloc

calloc的初始化如下:void* calloc (size_t num, size_t size); 它为每个大小为size的num字节开辟空间,并初始化为0。其与malloc的区别是它会将所有字节初始化为0。

realloc

有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时 候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤ ⼩的调整。

函数的原型如下:

void* realloc (void* ptr, size_t size); ptr是要调整的内存地址,size为调整后的大小。返回值为调整后内存的起始位置。

这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。 realloc在调整内存空间的是存在两种情况:  情况1:原有空间之后有⾜够⼤的空间,空间续上,然后再返回起始空间的地址就可以了 情况2:原有空间之后没有⾜够⼤的空间

如果是情况一,增加空间直接追加在原有空间后面。 原有数据位置不发生变化,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。(满足新的大小要求)会将原来的数据拷贝一份到新的空间,释放旧的空间(realloc会主动把这块空间释放掉)。这样函数返回的是⼀个新的内存地址。

还有一种情况,调整失败,返回的是空指针。如下:

如果realloc调整失败了,空指针放到p里面,p原来还维护20个字节,现在20个字节释放了,也找不到了。

所以选择用新的指针ptr来接收新的空间地址

realloc函数可以完成和malloc一样的功能:

realloc(NULL,20);==malloc(20); 

下面举一个失败的案例:

  not enough space

动态内存的常见错误:

1对NULL指针的解引⽤操作

代码语言:javascript
复制
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}

原因:malloc的返回值需要判断

修改:

代码语言:javascript
复制
void test()
{
int *p = (int *)malloc(INT_MAX/4);
if(p==NULL)
{
    perror("malloc");
    return 1;
}
assert(p);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}

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

代码语言:javascript
复制
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);
}

对⾮动态开辟内存使⽤free释放,是不能够运行的

使⽤free释放⼀块动态开辟内存的⼀部分

代码语言:javascript
复制
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}

必须是起始位置,报错。

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

代码语言:javascript
复制
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

在释放完之后,及时把p置为空指针。

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

代码语言:javascript
复制
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}

在test函数运行完成之后,就找不到malloc的一百个空间了。一旦出去,局部变量就销毁了。不用也不释放,就造成了内存泄漏的问题。

malloc,realloc,calloc申请的空间如果不想使用,都可以使用free来释放,当程序结束的时候,也会由操作系统释放。

尽量做到:谁申请的空间谁释放,如果不能释放,要告诉使用的人记得释放。

malloc和free成对出现。

但是架不住指针的空间可能提前释放,如下:

动态内存经典笔试题分析

 str为空指针。GetMemory本身是传值调用,p内也是NULL。malloc出空间的地址放到p指针中,p销毁,还给操作系统。当回来的时候,str依然为空指针。空指针没有指向有效的空间,

当结束最上面一个程序运行的时候,str的空间已经还给操作系统了,str已经是空指针了。出现对空指针的解引用操作,程序崩溃,不会打印。

那么如果对这个程序进行修改,使其正确呢?那么我们把str的地址传给GetMemory,char*是一级指针变量,那么要用二级指针接收

我们对修改后的程序,进行解释。

首先 创建一个指针,叫作str。str里储存一个空指针NULL。p指针里储存了一个地址,就是ptr的地址,指向ptr。而p malloc出100个空间的大小,所以相当于str指向这块空间。

这个代码有什么问题呢? p所指向的空间还给操作系统。

上面这个代码,应该free(str);之后把str置为空。

如上图所示,在free之后置为空。 

柔性数组

柔性数组有哪些特点呢?

结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。

sizeof返回的这种结构⼤⼩不包括柔性数组的内存。

包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

柔性数组的使用:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
return 0;
}

 用realloc让数组变大变小

柔性数组的优势:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
int i;
int *p_a;
}type_a;
int main()
{
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{
p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;
return 0;
}

上述 代码1 和 代码2 可以完成同样的功能,但是 ⽅法1 的实现有两个好处: 第⼀个好处是:⽅便内存释放 如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给用户。用户调⽤free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能依靠用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给用户⼀个结构体指针,用户做⼀次free就可以把所有的内存也给释放掉。 第⼆个好处是:这样有利于访问速度 连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • malloc
  • free
  • calloc
  • realloc
    • 对同一块动态内存多次释放
    • 动态内存经典笔试题分析
    • 柔性数组
      • 柔性数组的使用:
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档