什么时候应该使用动态内存分配函数而不是直接变量声明



下面是一个直接变量声明的例子:

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;
}

如果我有选择的话,我会一直使用直接变量声明,因为代码看起来更可读。在什么情况下动态内存分配更合适?

在什么情况下动态内存分配更合适?

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

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

  1. 如果我们想要一个在运行时重新调整大小的数据结构,我们需要使用动态内存分配

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

  3. 通常堆栈大小会受到适度限制。如果您想要创建和使用一个巨大的数组,最好使用动态内存分配。

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

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

直接声明位于堆栈上,并在离开函数时删除。return语句中所发生的是,在销毁变量之前创建一个变量的副本。

考虑这个例子:

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

Vs。在堆栈

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

在第一种情况下,只创建一个人并将其添加到office和family。现在,如果这个人被删除了,他在办公室和家庭中都是无效的,而且,如果他的数据被改变了,它在两者中也都被改变了。

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

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

"如果我有选择的话,我会一直使用直接变量声明"

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

  • 堆栈空间是有限的,如果你需要更多的空间,你必须自己分配它(想想大数组,像struct huge_struct array[10000])。要了解堆栈有多大,请参阅此页。注意,实际的堆栈大小可能不同。
  • C传递参数,并逐个返回值。如果您想返回一个数组,它会衰变成一个指针,那么您最终将返回一个指向超出作用域(无效)的数组的指针,从而导致UB。像这样的函数应该分配内存并返回指向它的指针。
  • 当你需要改变一些东西的大小(realloc),或者你不知道你需要多少内存来存储一些东西。你在堆栈上声明的数组是固定大小的,指向内存块的指针可以重新分配(malloc新块>=当前块大小+ 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<count;++i)
        return_val[i] = vals[i] * factor;
    return return_val;
}
//valid
double *get_double_values(const double *vals, double factor, size_t count)
{
    double *return_val = malloc(count * sizeof *return_val);
    if (return_val == NULL)
        exit( EXIT_FAILURE );
    for (int i=0;i<count;++i)
        return_val[i] = vals[i] * factor;
    return return_val;
}

必须调整对象的大小:

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;
}

在这个场景中,我们的代码调用了一些异步处理的库函数。我们可以传递一个回调函数来处理库的结果。该库还为我们提供了一种通过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的函数,它有两个参数:一个叫做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;
}

当您打算将数据传输到局部作用域外(例如函数)时,需要动态内存分配。
此外,当您无法事先知道需要多少内存时(例如用户输入)。
最后,当你知道需要的内存量但是它溢出了堆栈。否则,由于可读性、运行时开销和安全性的原因,不应该使用动态内存分配。

相关内容

  • 没有找到相关文章

最新更新