在函数中隐藏内存分配是不好的做法吗?

时间:2017-09-05 21:21:37

标签: c malloc

我是否希望用户提供足够大小的内存块,比如将文件复制到缓冲区?或者我应该自己分配内存,并希望用户在完成后释放它?例如,函数strdup()本身分配内存,但函数fread()只需要一个足够大的缓冲区。

3 个答案:

答案 0 :(得分:5)

这取决于 - 我已经看到C API使用了所有类型的模式,例如:

  • 需要提供缓冲区和缓冲区大小的函数,并返回所需的大小(以便在被截断时调整缓冲区大小);如果您只是询问缓冲区应该有多大,那么其中许多允许将NULL作为缓冲区传递;这允许调用者使用现有缓冲区或分配适当大小的缓冲区,尽管有两个调用;
  • 单独的函数以获得所需的大小并填充缓冲区;与上述相同,但界面更清晰;
  • 需要缓冲区和缓冲区大小的函数,但如果将NULL作为缓冲区传递,则可以自行分配缓冲区;最大的灵活性和简洁性,但功能签名可能会令人困惑;
  • 只返回新分配的字符串的函数;简单易用,避免因无保护的截断而产生的错误,但如果性能受到关注则不灵活;另外,要求调用者记住释放返回的值,如果使用堆栈分配的缓冲区,则在上述情况下可以避免;
  • 返回指向静态缓冲区的指针的函数,然后调用者负责对它做任何事情;极易使用,极易误操作;在多线程(需要线程本地存储)和重入是一个问题的情况下需要小心。

最后一个通常是一个坏主意 - 它带来了重入和线程安全的问题;可以使用之前的那个但可能会造成效率问题 - 如果我已经有足够大的缓冲区,我通常不想浪费时间分配。所有其他人通常都很好。

但是除了接口的细节之外,如果你分配东西和/或返回指针最重要的一点是清楚地记录谁拥有指向的内存 - 它是你库中的静态对象吗?它是指向调用者提供的对象内部的指针吗?它是动态分配的东西吗?调用者是否负责释放它?它只是作为参数提供的缓冲区吗?

最重要的是,如果你分配了东西,总是指定如何解除分配;请注意,如果要构建一个可以编译为dll /的库,那么提供自己的释放函数(即使它只是free的包装器)是个好主意,以避免不同版本之间的不匹配C运行时在同一进程中运行。此外,它避免将您的代码绑定到C库分配器 - 今天它可能没问题,明天可能会发现使用自定义分配器可能是一个更好的主意。

答案 1 :(得分:3)

  

在函数中隐藏内存分配是不好的做法吗?

有时。

答案显示何时可以滥用代码来详细说明允许函数在内存分配中完全自由的缺陷之一。

当函数本身确定所需的大小时会发生经典案例,因此调用代码缺少事先提供内存缓冲区所需的信息。

getline()就是这种情况,其中流内容会限制分配的大小。这个问题,特别是当流是stdin时,对内存分配的控制是给外部源的,而不受调用代码 - 程序的限制。外部输入可能会压倒内存空间 - 一个黑客。

使用修改后的功能,例如ssize_t getline_limit(char **lineptr, size_t *n, FILE *stream, size_t limit);,该功能仍可提供正确大小的分配,但仍可防止黑客滥用。

#define LIMIT 1000000
char *line = NULL;
size_t len = 0;
ssize_t nread;

while ((nread = getline_limit(&line, &len, stdin, LIMIT)) != -1) {

这不是一个问题的例子是一个有限使用的分配。

// Convert `double` to its decimal character representation allocating a right-size buffer
// At worst a few thousand characters
char *double_to_string_exact_alloc(int x)

执行内存分配的函数需要一定程度的控制,以防止使用特定参数或任务性质进行无限的内存分配。

答案 2 :(得分:2)

C库函数避免返回已分配的内存。这至少是为什么strdup不是标准库的一部分,以及用于读取无限长C字符串的流行scanf扩展的部分原因。

您的图书馆可以选择其中一种方式。使用预先分配的缓冲区更灵活,因为它允许用户通过静态分配的缓冲区。这种灵活性是有代价的,因为用户的代码变得更加冗长。

如果您选择动态地为自定义结构分配内存,最好在对用户不必要的情况下创建一个匹配函数来解除分配结构。