应该何时使用动态内存分配函数与直接变量声明?

以下是直接变量声明的示例。

double multiplyByTwo (double input) { double twice = input * 2.0; return twice; } 

下面是动态内存分配的示例。

 double *multiplyByTwo (double *input) { double *twice = malloc(sizeof(double)); *twice = *input * 2.0; return twice; } 

如果我有一个选择,我会一直使用直接变量声明,因为代码看起来更具可读性。 什么时候动态内存分配更合适?

“如果我有选择,我会一直使用直接变量声明”

你也应该这样做。 除非需要,否则不要使用堆内存。 这显然引出了一个问题:我什么时候需要动态内存?

  • 堆栈空间有限,如果你需要更多的空间,你必须自己分配它(想想大数组,比如struct huge_struct array[10000] )。 要了解堆栈的大小, 请参阅此页面 。 请注意,实际堆栈大小可能不同。
  • C传递参数,并按值返回值。 如果要返回一个衰减为指针的数组,最终会返回一个指向超出范围(无效)的数组的指针,从而产生UB。 像这样的函数应该分配内存并返回指向它的指针。
  • 当你需要改变某些东西的大小( realloc ),或者你不知道你需要多少内存来存储东西。 你在堆栈上声明的数组大小是固定的,可以重新分配一个指向内存块的指针( malloc new block> =当前块大小+ memcpy + free原始指针基本上是realloc所做的)
  • 当某段内存需要在各种函数调用中保持有效。 在某些情况下,全局变量不会(思考线程)。 此外:全局几乎在所有情况下都被认为是不好的做法。
  • 共享库通常使用堆内存。 这是因为他们的作者不能假设他们的代码将有大量的堆栈空间随时可用。 如果你想编写一个共享库,你可能会发现自己编写了大量的内存管理代码

所以,举一些例子来澄清:

 //perfectly fine double sum(double a, double b) { return a + b; } //call: double result = sum(double_a, double_b); //or to reassign: double_a = (double_a, double_b); //valid, but silly double *sum_into(double *target, double b) { if (target == NULL) target = calloc(1, sizeof *target); *target = b; return target; } //call sum_into(&double_a, double_b);//pass pointer to stack var //or allocate new pointer, set to value double_b double *double_a = sum_into(NULL, double_b); //or pass double pointer (heap) sum_into(ptr_a, double_b); 

返回“数组”

 //Illegal double[] get_double_values(double *vals, double factor, size_t count) { double return_val[count];//VLA if C99 for (int i=0;i 

必须调整对象的大小:

 double * double_vals = get_double_values( my_array, 2, sizeof my_array/ sizeof *my_array ); //store the current size of double_vals here size_t current_size = sizeof my_array/ sizeof *my_array; //some code here //then: double_vals = realloc( double_vals, current_size + 1 ); if (double_vals == NULL) exit( EXIT_FAILURE ); double_vals[current_size] = 0.0; ++current_size; 

需要长时间保持范围的变量:

 struct callback_params * some_func( void ) { struct callback_params *foo = malloc(sizeof *foo);//allocate memory foo->lib_sum = 0; call_some_lib_func(foo, callback_func); } void callback_func(int lib_param, void *opaque) { struct callback_params * foo = (struct callback_params *) opaque; foo->lib_sum += lib_param; } 

在这种情况下,我们的代码调用了一些异步处理某些东西的库函数。 我们可以传递一个回调函数来处理库的结果。 lib还为我们提供了一种通过void *opaque将一些数据传递给该回调的方法。

call_some_lib_func将具有以下行的签名:

 void call_some_lib_func(void *, void (*)(int, void *)) 

或者以更易读的格式:

 void call_some_lib_func(void *opaque, void (*callback)(int, void *)) 

所以它是一个名为call_some_lib_func的函数,它接受2个参数:一个名为opaquevoid * ,一个返回void的函数的函数指针,并将一个int和一个void *作为参数。

我们需要做的就是将void *转换为正确的类型,我们可以操纵它。 另请注意, some_func返回指向不透明指针的指针,因此我们可以在任何需要的地方使用它:

 int main ( void ) { struct callback_params *params = some_func(); while (params->lib_sum < 100) printf("Waiting for something: %d%%\r", params->lib_sum); puts("Done!"); free(params);//free the memory, we're done with it //do other stuff return 0; } 

什么时候动态内存分配更合适?

在编译时不知道分配大小时,我们需要使用动态内存分配。

除了上述情况,还有其他一些场景,比如

  1. 如果我们想要一个在运行时可重新resize的数据结构,我们需要进行动态内存分配。

  2. 动态分配的内存的生存期保持有效,除非它是free() d。 有时,从函数调用返回变量的某个地址时会很方便,否则,使用auto变量,它将超出范围。

  3. 通常,堆栈大小会受到适度限制。 如果要创建和使用大型数组,最好使用动态内存分配。 这将从堆中分配内存。

使用malloc进行动态内存分配会将内存放在堆上,因此在离开函数时不会将其销毁。

稍后您需要手动释放内存。

直接声明落在堆栈上并在离开函数时被删除。 return语句中发生的是变量的副本在销毁之前生成。

考虑这个例子:

在堆上

 void createPeople(): struct person *p = makePerson(); addToOffice(p); addToFamily(p); 

比。 在堆栈上

 void createPeople(): struct person p = makePerson(); addToOffice(p); addToFamily(p); 

在第一种情况下,只创建一个人并将其添加到办公室和家庭。 现在,如果该人被删除,则在办公室和家庭中都会失效,而且,如果他的数据被更改,则两者都会被更改。

在第二种情况下,为办公室和家庭创建该人的副本。 现在,您可能会在办公室中更改副本的数据,而且系列中的副本保持不变。

所以基本上如果你想给几个方访问同一个对象,它应该在堆栈上。

当您打算从本地范围(例如函数)传输数据时,需要动态内存分配。
此外,当您无法预先知道需要多少内存时(例如用户输入)。
最后,当你知道所需的内存量但它溢出堆栈时。 否则,由于可读性,运行时开销和安全性,不应使用动态内存分配。