为什么或何时需要在C中动态分配内存?

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

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

作为旁注,有什么区别:

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

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

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

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

您并不总是知道在编译时需要留出多少内存。 想象一下处理一个数据文件(比如一个时间序列的温度),其中文件中的记录数量是不固定的。 您可以只有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类型)。

当您不知道内存的最坏情况需求时,需要动态分配。 然后,不可能静态分配必要的内存,因为你不知道你需要多少。

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

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

作为旁注,有什么区别: ptr_one = (int *)malloc(sizeof(int))int *ptr_one = malloc(sizeof(int))

看到这个 。

首先,我知道这可能是一个荒谬的问题,因为动态内存分配是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. } 

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

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]注意到如果在堆栈上定义了这个,那么一旦弹出这个堆栈帧,就不再分配该arrays的所有内存地址。 意思是,另一个堆栈帧(可能是下一个堆栈帧)将使用那些内存地址(或它的某个子集)。

备注:从我的代码片段中, ptr_block存储在堆栈中:因此&ptr_block是堆栈地址,但ptr_block的值是从内存池的某个位置。