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

本文详细介绍了动态内存分配的四个关键函数:malloc、free、calloc和realloc,讨论了它们的功能、用法以及常见错误,包括内存初始化、释放、大小调整和柔性数组的应用。同时强调了内存管理的最佳实践,如谁申请的空间谁释放的原则。
摘要由CSDN通过智能技术生成

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

首先,我们已经掌握了一种开辟内存的方式,就是直接使用int i=20;

但是这样开辟空间有两个特点,

1:空间开辟大小是固定

2:数组在创建时,必须设定数组的长度,数组空间的大小一旦确定就不能更改

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

目录

malloc

free

calloc

realloc

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

动态内存经典笔试题分析

​编辑

柔性数组

柔性数组的使用:


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的是要释放空间的起始地址,例如下面的案例,传递的不是起始地址,就无法释放:

2e4c62a2089448d2874d5c9649551601.png

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

calloc

calloc的初始化如下:

void* calloc (size_t num, size_t size);

它为每个大小为size的num字节开辟空间,并初始化为0。

其与malloc的区别是它会将所有字节初始化为0。(malloc需要主动检查是否为空指针,并置空)

realloc

有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时
候内存,我们⼀定会对内存的⼤⼩做灵活的调整。

那 realloc 函数就可以做到对动态开辟内存⼤⼩的调整。

函数的原型如下:

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


ptr是要调整的内存地址,size为调整后的大小。返回值为调整后内存的起始位置。

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

realloc在调整内存空间的是存在两种情况:
情况1原有空间之后有⾜够⼤的空间,空间续上,然后再返回起始空间的地址就可以了


情况2:原有空间之后没有⾜够⼤的空间

96b13926749b424588d2e1bda956e4c8.png

如果是情况一,增加空间直接追加在原有空间后面。 原有数据位置不发生变化,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在空间上另找⼀个合适⼤⼩的连续空间来使⽤。(满足新的大小要求)

会将原来的数据拷贝一份到新的空间,释放旧的空间(realloc会主动把这块空间释放掉)。这样函数返回的是⼀个新的内存地址。

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

f412a254f6284ae68432e3621e7e9548.png

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

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

1324cfce108b403c9e43e028c4b61fe2.png

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

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

下面举一个失败的案例:

3c269b8d92444735aca694189f1262a3.png

  not enough space

动态内存的常见错误:

1 对NULL指针的解引⽤操作

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

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

修改:

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

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

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);
}

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

使⽤free释放⼀块动态开辟内存的⼀部分必须是起始位置,报错。

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

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

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

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

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

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成对出现。

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

791e8269d9ff4a0dbfb3cf6838dea58e.jpg

在这种情况下,在free指针p之前,空间就已经被return释放掉了,这是就可以不用再单独使用free函数将p释放了。

动态内存经典笔试题分析

1malloc出的空间被销毁

66bcfec1e89843e4b653b1cac703cdda.png

 832f758e0724429f959cc6bd30a62f87.jpg

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

当结束最上面一个程序运行的时候,str的空间已经还给操作系统了,str已经是空指针了。

出现对空指针的解引用操作,程序崩溃,不会打印。

那么如果对这个程序进行修改,使其正确呢?

那么我们把str的地址传给GetMemory,char*是一级指针变量,那么要用二级指针接收

d63a82b8135b44ecb21c0cc5f63710c3.png

2b203b91af5f40d880898dde8d24201e.jpg

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

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

2p指向的空间还给操作系统无法打印

f15c82b4a512457cbe26f3a8a6db1064.png

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

3

上面这个代码,应该free(str);

之后把str置为空。而且上面这份代码,传num参数也便于修改开辟空间的大小。

eaccb9b522c048159a5199d06100b20e.png

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

柔性数组

柔性数组有哪些特点呢?

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

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

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

柔性数组的使用:

#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让数组变大变小

柔性数组的优势:

#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就可以把所有的内存也给释放掉。


第⼆个好处是:这样有利于访问速度
连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。


 

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值