动态内存分布——malloc,calloc,realloc,free的使用。以及关于动态内存的常见错误。

简介: 动态内存分布——malloc,calloc,realloc,free的使用。以及关于动态内存的常见错误。

我们知道内存的使用方式,可以在栈区,静态区,堆区,申请空间来储存变量。

但是他们这些内存区所存储的东西是不一样的。


image.png


创建一个变量:


int a=10;int arr[]={0};——局部变量——栈区;


int a=10;int arr[]={0};——全局变量——静态区;


但是面两种申请的空间都是固定的,例如我们在使用数组的时候,有时候发现数组的申请的空间小了,又回去改数组的大小,改了之后又发现用不完,就回给我们的空间造成浪费,数组而不能达到随心所欲的修改申请给我们带来不便。这就是我们今天学习的动态内存分布就可以很好的解决相这个问题,当我们的申请的空间小了,我们可以直接扩充,用不完的可以直接减少。


1.malloc


头文件: #include<stdlib.h>


函数框架:void*malloc(size_t size)。


返回值:函数的返回类型位空指针类型,可根据具体的申请的和需要强行转换指针类型,返回的指针指向申请的空间的是首地址。


参数:函数的参数是一个无符号整型数字(size_t  size),代表申请空间的大小,单位为字节。


如果申请成功函数返回申请在堆区空间的地址,如果申请不成功,就会返回NULL(空指针)。


实例:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(sizeof(int) * 10);//申请了一个可以存放10个int类型的空间;
  if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL).
  {
    printf("申请失败\n");
  }
  else
  {
    printf("申请成功\n");
  }
  return 0;
}


这里申请成功了



让我们看一下申请不成功的情况

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(100000000000000000*10000000000000000000);//申请了一个非常大的空间,
                                   //已经超出我们堆区的最大存储空间,
                                   //肯定会申请失败;
  if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL).
  {
    printf("申请失败\n");
  }
  else
  {
    printf("申请成功\n");
  }
  return 0;
}



接下来我们看一下malloc申请空间的使用:

看代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(sizeof(int)*10);//申请可以存放10个int型变量的空间;
  if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL).
  {
    printf("申请失败\n");
    return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作;
  }
  else
  {
    int i = 0;
    for (i = 0; i < 10; i++)
    {
      *(p + i) = i;//申请的10个空间,赋值0——9;
    }
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p+i));//证明我们我们的数字已经存进去,打印出我们存进去的数字。
      }
  }
  free(p);//当我们的动态申请的空间不再使用的时候,就应该还给操作系统,所以就是用free,对申请的空间释放。
  p = NULL;//但是我们申请的空间是还给操作系统了,但是指针 p 仍旧可以找到那块空间,所以我们把 p赋值成空指针,将指针 p 与空间完全断开联系。
  return 0;
}


关于free:



malloc与free,是成对使用的。


2.calloc

头文件:#include<stdlib.h>


函数框架:void*calloc(size_t num,size_t size);


calloc的功能与malloc基本一样,也是从堆区申请一块空间,但是区别就在于calloc会在返回地址之前把申请的空间全部赋值为0;


参数:size_t num——无符号整形数,代表申请空间的个数,size_t size——无符号整形数,代表


申请每个空间的大小,单位为字节。


返回值:函数的返回类型位空指针类型,可根据具体的申请的和需要强行转换指针类型,返回的指针指向申请的空间的是首地址。


如果申请成功函数返回申请在堆区空间的地址,如果申请不成功,就会返回NULL(空指针)。


实例2.

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)calloc(10,sizeof(int));//申请可以存放10个int型变量的空间;
  if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL).
  {
    printf("申请失败\n");
    return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作;
  }
  else//申请成功开始使用
  {
    int i = 0;
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));//查看在为赋值的时候申请的空间里面存储是不是0;
      *(p + i) = i;//申请的10个空间,赋值0——9;
    }
    printf("\n");
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));//证明我们的数字已经存进去,打印出我们存进去的数字。
    }
  }
  free(p);
  p = NULL;//用完别忘记释放哦。
  return 0;
}


df33e7a112c84322bd054ea9e0d3b457.png


3.realloc


realloc函数使我们动态内存管理更加灵活。


有时会发现过去申请的空间太小了,有时候我又会觉得申请的空间太大了,那为了合理的内存,我们呢一定会对内存进行灵活的调整,realloc就可以做到对内存大小的调整。


函数原型:void* realloc(void*ptr,size_t size)


参数:void*ptr ,ptr为要调整的空间的地址,size_t size为调整之后的新大小。


返回值:调整之后的内存的起始位置,


函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间里面去。


realloc使用实例:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)calloc(10, sizeof(int));//申请可以存放10个int型变量的空间;
  if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL).
  {
    printf("申请失败\n");
    return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作;
  }
  else//申请成功开始使用
  {
    int i = 0;
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));//查看在为赋值的时候申请的空间里面存储是不是0;
      *(p + i) = i;//申请的10个空间,赋值0——9;
    }
    printf("\n");
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));//证明我们的数字已经存进去,打印出我们存进去的数字。
    }
  }
  printf("\n");
  //这时我们发现内存不够
  int* p1 = (int*)realloc(p, sizeof(int) * 15);//我们将申请空间扩大到可以存放15个int型数字。
  if (p1 == NULL)
  {
    printf("申请失败");
    return 0;
  }
  for (int i = 10; i < 15; i++)
  {
    *(p1 + i) = i;//将新申请的空间赋值10——14;
  }
  for (int i = 0; i < 15; i++)
  {
    printf("%d ", *(p1 + i));
  }
  free(p1);
  p1 = NULL;
  return 0;
}


关于realloc的注意:


如果原空间之后有足够的空间就可以直接追加,然后返回调整之后的空间的起始位置地址;


如果原空间后面没有足够的空间可以用来追加,则realloc就会重新找一个新的内存区域,将元数据拷贝过去再追加,最后返回新空间的起始位置地址;


要用一个新的指针变量来接受realloc函数的返回值;


关于realloc在内存追加空间的时候,后面没有足够的大小用来追加会重新找一个新的内存区域,将元数据拷贝过去再追加,最后返回新空间的起始位置地址;


实例:

#include<stdio.h>
#include<stdlib.h>
int main()//(1)后面有足够的空间追加;
{
  int* p = (int*)malloc(1);//申请可以存放1个字节的空间;
  if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL).
  {
    printf("申请失败\n");
    return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作;
  }
  printf("%p\n", p);
  int* p= (int *)realloc(p, 2);//追加一个字节,后共2个字节;
  printf("%p\n", p);
  free(p);
  p=NULL;
  return 0;
}



#include<stdio.h>
#include<stdlib.h>
int main()//(1)后面没有足够的空间追加;
{
  int* p = (int*)malloc(1);//申请可以存放1个字节的空间;
  if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL).
  {
    printf("申请失败\n");
    return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作;
  }
  printf("%p\n", p);
  int* p= (int *)realloc(p, 10000);//追加字节,后共10000个字节;
  printf("%p\n", p);
  free(p);
  p=NULL;
  return 0;
}



常见的动态内存的错误

1.对空指针解引用

void test()
{
  int* p = (int*)malloc(1000000000 * 10000000*10000000);//由于申请的空间过大而申请失败返回空指针;
  *p = 20;//当p为NULL时就会有问题;
  free(p);
  p = NULl;
}

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

int* p = (int*) = malloc(sizeof(int) * 5);//申请5个int的空间
if (p = NULl)
{
  exit;
}
for (int i = 0; i < 10; i++)//却访问10个int的空间
{
  *(p + i) = i;
}

3.对非动态开辟的空间进行free释放

void test()
{
  int a = 10;
  int* P = &a;
  free(p);//OK?
}

4.使用free释放一块空间的一部分

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

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

void Get()
{
  int*p = (char*)malloc(100);
  free(p);
  free(p);
}

6.动态内存忘记释放

7.

void Get(char* p)
{
  p = (char*)malloc(100);
}
void test()
{
  char* str = NULL;
  Get(str);//以变量的方式传参;无法做到str的修改;
  strcpy(str, "hello");
  printf(str);
}


运行的时候程序会崩溃,而且存在内存泄漏;

str以值的形式传参给p;p是Get的函数的形参,只能在函数内部有效,等Get函数返回之后,动态开辟的空间还未释放,并且无法找到,就会存在内存泄漏。

相关文章
|
1月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
191 1
|
3月前
|
存储 安全 编译器
Go 内存分布
该文章深入分析了Go语言中值的内存分布方式,特别是那些分布在多个内存块上的类型,如切片、映射、通道、函数、接口和字符串,并讨论了这些类型的内部结构和赋值时的行为,同时指出了“引用类型”这一术语在Go中的使用可能会引起的误解。
53 5
Go 内存分布
|
1月前
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
70 0
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
385 0
|
29天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
60 1
|
1月前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
1月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
1月前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
42 4
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
56 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配