分配内存以供函数使用的最佳实践 - malloc内部或外部?

时间:2012-11-18 17:05:07

标签: c malloc

在我使用C编码的经历中,我已经看到了两种为函数传递参数的方法:

    在调用函数之前
  1. malloc

  2. malloc内部函数(变量在调用函数之前未初始化)

  3. 我特别喜欢第二种形式。但是虽然我是唯一一个编写程序代码的人,但我知道,但其他一些人不知道,可能会导致2 malloc和内存泄漏。

    所以,我的问题是:最佳做法是什么?

4 个答案:

答案 0 :(得分:9)

在调用者中分配内存更加灵活,因为它允许调用者使用静态或自动存储而不是动态分配,并且无需处理被调用者中分配失败的情况。另一方面,让呼叫者提供存储需要呼叫者提前知道大小。如果将大小编译为调用者作为常量,并且被调用者在库中,以后更新为使用更大的结构,那么事情将会发生可怕的破坏。当然,您可以通过提供第二个函数(或库中的外部变量)来检索必要的大小来避免这种情况。

如有疑问,您可以随时提供两项功能:

  1. 使用来电者提供的存储的主要功能。
  2. 一个包装器函数,它分配适量的存储空间,使用它调用#1中的函数,并将指针返回给调用者。
  3. 然后调用者可以自由选择哪种方法更适合特定用例。

答案 1 :(得分:8)

我个人非常支持你的第一个命题(只要有可能)正交性。请看以下示例:

extern void bar(int *p, int n);

void foo(int n)
{

   int *p = malloc(n * sizeof *p);

   // fill array object
   bar(p, n);

   // work with array elements

   /* ... */

   // array no longer needed, free object
   free(p);
}

这是正交的。 mallocfree在相同的词法范围内调用,该范围清晰可读。另一个优点是您可以向bar函数传递具有不同存储持续时间的数组,例如具有自动或静态存储持续时间的数组。你让bar只关注它所做的工作,让另一个函数管理数组分配。

请注意,这也是所有标准C功能的工作方式:它们从不显示来呼叫malloc

答案 2 :(得分:3)

我用来决定的标准是:

  • 如果被调用函数外部的代码可以知道要分配多少内存,那么最好让调用代码分配内存。

  • 如果被调用函数外的代码无法知道要分配多少内存,那么被调用函数必须进行内存分配。然后可能会有第二个函数可用于释放第一个函数(“被调用”函数)返回的内存,除非它只需要一个free()。功能文档应该清楚说明。

例如,如果被调用函数正在从文件中读取完整的树结构,则该函数必须分配内存。但是,还会有一个用于释放内存的伴随函数(因为被调用的代码知道如何操作并且调用代码不需要知道)。

另一方面,如果被调用函数正在将整数和浮点值的简单列表读入固定大小的结构中,那么使调用函数分配内存要好得多。请注意,我跳过'字符串'!如果字符串在结构中具有固定大小,那么调用函数可以进行分配,但是如果字符串具有可变大小,则可能被调用函数进行分配。

标准C库具有像fgets()这样的函数,它们希望调用代码分配要使用的内存。调用序列告诉fgets()有多少可用空间。如果没有提供足够的内存,则会遇到问题。 (fgets()的问题在于,您可能只获得一行文本的开头,而不是整行文本。)

POSIX 2008库提供了getline(),它将为该行分配足够的空间。

asprintf()及相关函数(请参阅TR24731-2)根据需要分配内存。 snprintf()函数没有 - 它被告知有多少可用空间,它只使用它,并说明它真正需要多少,如果你没有提供足够的空间,你需要注意空间并对它做一些事情(分配更多的空间并再试一次,或者轻率地忽略截断的值并继续,好像什么都没有出错)。

答案 3 :(得分:1)

信息隐藏的原理表明,分配内存最好在函数内完成。

如果你看一下 stdio.h 的工作原理:

FILE *myFile;

myFile = fopen("input.txt", "r");

if (!myFile) {
  fprintf(stderr, "Error opening input.txt for reading.\n");
  // other exit handling close
}
else {
  // code to read from file
  fclose(myFile);
}

库调用分配内存,该内存包含有关您正在使用的文件的信息,并返回指向该结构的指针。调用者负责以后释放该内存(通过调用 fclose )。

在整个标准C库中重复此模式。

要求调用者分配和释放内存至少有两个缺点:

  1. 主叫方需要额外的代码。
  2. 如果分配的结构大小发生变化,则需要重新编译调用代码(至少)或更改调用代码。