动态内存管理(想要了解动态内存管理,只需要看这一篇就足够了!)

简介: 动态内存管理(想要了解动态内存管理,只需要看这一篇就足够了!)

那么不废话,让我们直接开始~~~

接下来我会从大致这三个方面来讲解动态内存管理:



1.动态内存管理的基本概念

       动态内存管理:它是C语言中一项重要的编程任务,它使得程序在运行时能够灵活地分配和释放内存,更好地适应不同的运行条件,通过动态内存管理,我们可以使用内存更高效、更灵活。

为了让读者能够更好的理解,现将其拆解开来理解:

       动态:即我们可以将申请的空间自由的变大变小以适应需求;

       内存:即我们申请的对象是内存空间;

       管理:我们需要对申请的内存空间进行管理操作;

2.为什么要有动态内存管理

       首先让我们来看一个实例:

int n = 1;      //在栈空间上开辟4个字节
int arr[5] = { 0 }; //在栈空间上开辟20个字节的连续空间

从上边的实例我们可以看出,我们创建变量是开辟空间的大小是固定的;当我们在船舰数组的时候,必须指定数组的长度,并且数组空间一旦确定了大小将不能调整其大小。但是有时候我们需要的空间大小是在程序运行的时候才能知道,那么上述创建空间大小的方式就不太适合了。

       因此引入了动态内存开辟的方式,让程序员自己可以申请和释放空间,这也就是为什么要有动态内存管理的原因。

3.动态内存的申请和释放

       现在我们已经了解动态内存管理的基本概念和为什么要有动态内存管理,那么接下来就是如何去申请动态内存,并且对其进行操作了。

       对于申请和释放动态内存空间,我们有这三个函数实现:malloc、free、calloc、realloc

       1.malloc

先让我们看一下官网对malloc函数的解释:

解释如下:

       1.该函数是向内存申请一块连续可用的空间,并返回指向这块空间的指针(首位置的地址);

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

       3.如果开辟失败,则返回一个NULL 指针;

       4.申请空间的大小的单位是字节,如果参数size 为0,malloc的行为是标准是未定义的,取决于编译器。

       5.使用该函数需要包含<stdlib.h>头文件

接下来让我们使用一个实例助你更好的了解malloc函数(如图):

//向内存申请20个字节大小的空间,并放入5个整数
#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(20);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  for (int i = 0; i < 5; i++)
  {
    *(p + i) = i + 1;
  }
  for (int k = 0; k < 5; k++)
  {
    printf("%d ", *(p + k));
  }
  return 0;
}

这里有两个注意点需要我们注意:

       1.我们将malloc的返回值进行了强制转换(返回值是void * 类型,但是我们将其强制转换成了int *,原因是我们已经知道了申请的空间中我们要放入什么数据,为了便于下面的操作,我们进行了对指针的强制转换);

       2.使用指针接收返回值后,我们判断了指针是否为NULL(原因是如果开辟失败,则返回一个NULL 指针,我们不能对NULL指针进行操作,所以直接停止程序);

至此我们就大致的了解了malloc函数的使用。

       2.free

有借有还,再借不难,我们向内存申请空间使用完成之后,就需要将申请的空间归还给操作系统,那么我们就需要使用free函数。

先让我们看一下官网对malloc函数的解释:

用一句话解释就是如果想要释放申请的空间,就将接收申请空间的地址的指针填入即可();

注:想要使用free函数释放申请内存空间,必须传递接收申请空间的地址的指针(即申请空间时返回值的地址,不能放入其他位置的地址)

       现在我们将上面的例子改进一下:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(20);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  for (int i = 0; i < 5; i++)
  {
    *(p + i) = i + 1;
  }
  for (int k = 0; k < 5; k++)
  {
    printf("%d ", *(p + k));
  }
  free(p);
  p = NULL;
  return 0;
}

从上面代码我们发现free完申请的空间之后,我们将接收申请空间的地址的指针置为了NULL,原因是放置该指针变为野指针。

至此我们就大致的了解了free函数的使用。

       3.calloc

calloc函数的使用以及用处和malloc大致相同,只有些许细节略微有些不同。

先让我们看一下官网对calloc函数的解释:

仿照着malloc函数,我们对calloc函数解释一下:

解释如下:

       1.函数的功能是开辟一块大小为num 个大小为size 的内存空间,返回指向这块空间的指针,并且把空间的每个字节初始化为0;

       2.如果开辟空间成功,则返回一个指向开辟好空间的指针(首位置的地址);

       3.如果开辟失败,则返回一个NULL 指针;

       4.申请空间的大小的单位是字节,如果参数size 为0,malloc的行为是标准是未定义的,取决于编译器。

       5.使用该函数需要包含<stdlib.h>头文件

我们使用一个实例进行进一步加深理解:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)calloc(5, sizeof(int));
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  for (int i = 0; i < 5; i++)
  {
    *(p + i) = i + 1;
  }
  for (int k = 0; k < 5; k++)
  {
    printf("%d ", *(p + k));
  }
  free(p);
  p = NULL;
  return 0;
}

事实上calloc函数在使用的形式上只是函数名称和参数改变了一下,其他与malloc函数全部一样。

注意:

       malloc函数和calloc函数内部的不同体现在了是否会把申请的内存空间的每个字节初始化为0!!!

如图所示:

malloc函数对申请的内存空间不进行初始化:

malloc函数对申请的内存空间进行初始化为0:

以上即malloc函数和calloc函数在存储内部的不同!!!

       4.realloc

讲了这么多对动态内存空间的申请,但是不是说可以对申请的空间大小进行修改吗?那么如何修改呢?

这就需要使用realloc函数:

先让我们看一下官网对realloc函数的解释:

我们对该函数进行讲解:

讲解如下:

       1.该函数会返回修改之后的内存空间的地址;

       2.ptr 是要调整的内存地址(即申请动态内存空间时返回值的地址),size 为调整之后新大小;

       3.如果修改空间成功,则返回一个指向修改好的空间的指针(首位置的地址);

       4.如果修改失败,则返回一个NULL 指针;

但是既然我们想要修改申请内存空间的大小,就可能会增加申请空间的大小,但由于我们申请的空间是连续的,就可能会遇到想要扩增空间时,后面的空间被使用的情况(如图):

那么这个时候该怎么扩增空间呢?

所以我们就需要分开进行讨论:

       1.后续空间充足时:

               要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化

       2.后续空间不充足时:

               其扩展的方法是:在堆空间上另找一个合适大小的连续空间,将已有数据先复制过去后,在进行扩增,然后返回一个新的内存地址。

以上我将就了解了realloc函数的使用和其是如何进行扩增空间的。那么我们接下来使用一个实例来使你更好的理解realloc函数:

//向内存申请20个字节大小的空间,并放入5个整数之后,
//将空间扩增为40字节大小,并接着上面的数字继续进行赋值
#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(20);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  for (int i = 0; i < 5; i++)
  {
    *(p + i) = i + 1;
  }
  for (int k = 0; k < 5; k++)
  {
    printf("%d ", *(p + k));
  }
  printf("\n");
  int* pc = (int*)realloc(p, 40);
  if (pc != NULL)
  {
    p = pc;
    pc = NULL;
    for (int i = 5; i < 10; i++)
    {
      *(p + i) = i + 1;
    }
    for (int i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));
    }
  }
  free(p);
  p = NULL;
  return 0;
}

从上面代码我们可以看到我们扩增完空间后,判断了其接收的指针是否是NULL之后才继续让p指针管理扩增后的空间,这样做的目的是为了防止扩增失败后返回NULL,可能会将之前管理内存空间的指针变为NULL,使之前开辟的空间失效。

以上我们就理解了realloc函数。

总结

       内存管理是一项非常重要的任务。动态内存管理是指在程序运行时分配和释放内存的过程。通过动态内存管理,我们可以根据需要分配适当的内存空间,并在不再需要时释放它。这使得程序更加灵活,并能够处理各种大小和形状的数据。


以上就是动态内存管理的所有内容了~~~

相关文章
|
10天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
32 6
|
1月前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
32 2
|
1月前
|
存储 程序员 编译器
C语言——动态内存管理与内存操作函数
C语言——动态内存管理与内存操作函数
|
1月前
|
存储 缓存 监控
深入了解MySQL内存管理:如何查看MySQL使用的内存
深入了解MySQL内存管理:如何查看MySQL使用的内存
310 1
|
1月前
|
存储 安全 程序员
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
63 3
|
2月前
|
Java
在 ArkTS 中,如何有效地进行内存管理和避免内存泄漏?
【9月更文挑战第25天】在ArkTS中,有效进行内存管理并避免内存泄漏的方法包括:及时释放不再使用的资源,如关闭监听器和清理定时器;避免循环引用,通过弱引用打破循环;合理使用单例模式,确保单例对象正确释放;及时处理不再使用的页面和组件,在卸载时清理相关资源。
|
1月前
|
Java C语言 iOS开发
MacOS环境-手写操作系统-16-内存管理 解析内存状态
MacOS环境-手写操作系统-16-内存管理 解析内存状态
35 0
|
2月前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
2月前
|
存储 并行计算 算法
CUDA统一内存:简化GPU编程的内存管理
在GPU编程中,内存管理是关键挑战之一。NVIDIA CUDA 6.0引入了统一内存,简化了CPU与GPU之间的数据传输。统一内存允许在单个地址空间内分配可被两者访问的内存,自动迁移数据,从而简化内存管理、提高性能并增强代码可扩展性。本文将详细介绍统一内存的工作原理、优势及其使用方法,帮助开发者更高效地开发CUDA应用程序。
|
3月前
|
Linux 测试技术 C++
内存管理优化:内存泄漏检测与预防。
内存管理优化:内存泄漏检测与预防。
58 2