【问题标题】:Why, or when, do you need to dynamically allocate memory in C?为什么或何时需要在 C 中动态分配内存?
【发布时间】:2013-08-15 13:36:25
【问题描述】:

动态内存分配是 C 编程中一个非常重要的话题。但是,我一直无法很好地解释这使我们能够做什么,或者为什么需要它。

我们不能只声明变量和结构,而不必使用 malloc() 吗?

作为旁注,有什么区别:

ptr_one = (int *)malloc(sizeof(int));

int *ptr_one = malloc(sizeof(int));

【问题讨论】:

  • 尝试编写一个程序,向用户询问一个数字(例如,班级中的学生人数),然后该次数要求他们输入姓名以编制学生名单类(然后按字母顺序对名称进行排序并将它们写入文件或其他内容)。您将名称存储在多大的数组中?
  • 关于强制转换 malloc 的返回值,您可能需要阅读此stackoverflow.com/questions/605845/…
  • 当您希望对象的生命周期独立于范围时,您需要动态存储持续时间。
  • 还因为默认情况下,堆栈不够大(~2MB)——还有其他更重要的原因。

标签: c malloc dynamic-memory-allocation


【解决方案1】:

您需要在以下情况下使用动态内存:

  • 您无法确定编译时要使用的最大内存量;
  • 您想分配一个非常大的对象;
  • 您想构建没有固定大小上限的数据结构(容器);

您并不总是知道在编译时需要留出多少内存。想象一下处理一个数据文件(比如温度的时间序列),其中文件中的记录数不固定。您可以拥有少至 10 条记录或多至 100000 条记录。如果要将所有数据读入内存以进行处理,则在读取文件之前您不会知道要分配多少内存。如果文件的结构使得第一个值是记录数,您可以执行以下操作:

size_t recs = 0;
double *temps = NULL;

FILE *fp = fopen ( filename, "r" );
if ( fp )
{
  if ( fscanf( fp, "%zu", &recs ) == 1 )
  {
    temps = malloc( sizeof *temps * recs );
    if ( temps )
    {
      // read contents of file into temps
    }
  }
}

有时你需要分配一个非常大的对象,比如

int ginormous[1000][1000][1000];

假设一个 4 字节的整数,这个数组将需要 4GB。不幸的是,堆栈帧(在大多数体系结构中保留局部变量)往往比这小得多,因此尝试分配这么多内存可能会导致运行时错误(并且通常会)。动态内存池(也称为堆)通常比堆栈大很多,更不用说任何一个堆栈帧。因此,对于令人讨厌的东西,您需要编写类似的东西

int (*ginormous)[1000][1000] = malloc( sizeof *ginormous * 1000 );

这样的请求仍有可能失败;如果你的堆足够碎片化,你可能没有一个足够大的连续块来处理请求。如有必要,您可以进行零碎分配;行在内存中不一定是相邻的,但更有可能您能够获取所需的所有内存:

int ***ginormous = malloc( sizeof *ginormous * 1000 );
if ( ginormous )
{
  for ( size_t i = 0; i < 1000; i++ )
  {
    ginormous[i] = malloc( sizeof *ginormous[i] * 1000 );
    if ( ginormous[i] )
    {
      ginormous[i][j] = malloc ( sizeof *ginormous[i][j] * 1000 );
      if ( ginormous[i][j] )
      {
        // initialize ginormous[i][j][k]
      }
    }
  }
}

最后,动态内存允许您构建可以随着您添加或删除数据而增长和缩小的容器,例如列表、树、队列等。您甚至可以构建自己的真正的“字符串”数据类型,它可以增长当您向其附加字符时(类似于 C++ 中的 string 类型)。

【讨论】:

    【解决方案2】:

    当您不知道最坏情况下的内存需求时,需要动态分配。那么,就不可能静态分配必要的内存,因为你不知道自己需要多少。

    即使您知道最坏情况的要求,仍可能需要使用动态内存分配。它允许多个进程更有效地使用系统内存。所有进程都可以静态提交其最坏情况下的内存需求,但这限制了系统上可以存在多少正在运行的进程。如果从来没有所有进程同时使用最坏情况的情况,那么系统内存不断地在未充分利用的情况下运行,这是一种资源浪费。

    至于您的附带问题,您不应该在 C 中将调用结果转换为 malloc()。它可以隐藏缺少声明的错误(在 C.99 之前允许隐式声明),并导致未定义行为。总是喜欢在没有演员表的情况下获得malloc() 的结果。 malloc() 被声明为返回 void *,而在 C 中,始终允许在 void * 和另一种指针类型之间进行转换(模类型限定符,如 const)。

    【讨论】:

      【解决方案3】:

      附带说明,ptr_one = (int *)malloc(sizeof(int))int *ptr_one = malloc(sizeof(int)) 之间有什么区别

      this

      首先,我知道这可能是一个荒谬的问题,因为动态内存分配是 C 编程中一个非常重要的话题。但是,我一直无法很好地解释这使我们能够做什么,或者为什么需要它。

      与堆栈相比,内存池(或更常见的堆)非常大。考虑以下两个示例,了解为什么在堆栈上使用内存池很有用:

      1. 如果您定义了一个数组并希望它在多个堆栈帧中持久存在,该怎么办?当然,您可以将其声明为全局变量,并将其存储在内存的全局数据部分中,但是随着程序变得越来越大,这将变得混乱。或者,您可以将其存储在内存池中。

      int *func( int k ) {
        assert( k >= 1 );
      
        int *ptr_block = malloc( sizeof( int ) * k );
      
        if ( ptr_block == NULL ) exit( EXIT_FAILURE );
      
        for ( int i = 0; i < k; i++ ) {
          ptr_block[ i ] = i + 1;
        }
      
        return ptr_block; // Valid.
      }
      

      ... 但是,如果您在堆栈上定义了数组,这将 起作用。原因是,一旦弹出堆栈帧,所有内存地址都可以被另一个堆栈帧使用(并因此被覆盖),而使用内存池中的内存将持续到用户(您或客户端)freed。

      2. 如果您想实现一个动态数组来处理读取任意大的数字序列怎么办?您将无法在堆栈上定义数组,您需要使用内存池。回想一下,将指针传递给结构,而不是结构本身(因为它们可能相当大),这是非常常见的(强烈推荐,除非您明确需要复制结构)。考虑这个动态数组的小实现:

      struct dyn_array {
        int *arr;
        int len;
        int cap;
      };
      
      typedef struct dyn_array *DynArray;
      
      void insert_item( int const item, DynArray dyn_arr ) {
        // Checks pre conditions.
        assert( dyn_arr != NULL );
      
        // Checks if the capacity is equal to the length. If so, double.
        if ( dyn_arr->cap == dyn_arr->len ) {
          dyn_arr->cap *= 2;
      
          DynArray new_dyn_arr = malloc( sizeof( int ) * dyn_arr->cap ); // [oo]
      
          // ... copy, switch pointers and free...
        }
      
        // ... insert, increase length, etc.
      }
      

      ... 在[oo] 线上,请注意,如果这是在堆栈上定义的,那么一旦弹出此堆栈帧,将不再分配数组的所有内存地址。这意味着,另一个堆栈帧(可能是下一个)将使用这些内存地址(或其中的某个子集)。

      备注: 从我的代码 sn-p 来看,ptr_block 存储在堆栈中:因此 &amp;ptr_block 是堆栈地址,但 ptr_block 的值在内存中的某个位置游泳池。

      【讨论】:

        猜你喜欢
        • 2019-04-20
        • 2021-11-16
        • 2019-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-12
        • 1970-01-01
        相关资源
        最近更新 更多