繁体   English   中英

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

[英]When should one use dynamic memory allocation function versus direct variable declaration?

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

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

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

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

因此,基本上,如果您想让多方访问同一对象,则该对象应该在堆栈中。

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

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

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

  • 堆栈空间是有限的,如果需要更多空间,则必须自己分配(请考虑大数组,例如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;
}

在这种情况下,我们的代码正在调用某些库函数,该函数异步处理某些内容。 我们可以传递一个回调函数来处理库文件的结果。 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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM