我应该期望用户提供足够尺寸的内存块,例如将文件复制到缓冲区中吗?还是我本人应该分配内存,并希望用户完成后释放记忆?例如,该函数strdup()
分配内存本身,但是该功能fread()
只期望一个足够大的缓冲区。
这取决于 - 我看到c apis使用各种模式,例如:
- 需要提供缓冲区和缓冲区大小的功能,然后返回所需的尺寸(以便如果截断,可以调整缓冲区的大小);其中许多允许将
NULL
作为缓冲区通过,如果您只是问缓冲区应该有多大。这允许呼叫者使用现有的缓冲区或分配适当尺寸的缓冲区,尽管有两个呼叫; - 单独的功能以获得所需的尺寸并填充缓冲区;与上面相同,但具有更清晰的接口;
- 需要缓冲区和缓冲区大小的功能,但如果
NULL
作为缓冲区传递,则可以分配缓冲区;最大的灵活性和简洁性,但功能签名会令人困惑; - 函数只返回新分配的字符串;易于使用并避免因截断而引起的错误,但如果担心性能,则不灵活;另外,要求呼叫者记住释放返回的值,如果使用堆栈分配的缓冲区;
- 函数将指针返回到静态缓冲区,然后呼叫者负责对其进行任何操作;极其易于使用,非常容易滥用;需要在多线程(需要线程本地存储)的情况下进行护理,并且是否需要重新进入。
最后一个通常是一个坏主意 - 它带来了重新进入和线程安全的问题;可以使用之前的一个,但可能会带来效率问题 - 如果我已经有足够大的缓冲区,我通常不想在分配中浪费时间。所有其他人通常都可以。
但是,除了界面的细节外,如果您分配内容和/或返回指针,最重要的是要清楚地记录谁拥有尖的内存 - 它是库中的静态对象吗?它是指向呼叫者提供的对象的某些内部指针吗?它是动态分配的东西吗?呼叫者负责释放它吗?只是作为参数提供的缓冲区吗?
最重要的是,如果您分配内容,请始终指定如何处理;请注意,如果您要构建可能被编译为DLL/因此编译的库,那么最好提供自己的DealLocation功能(即使它只是围绕free
的包装器),以避免C运行时不同版本的不匹配在同一过程中。此外,它避免将您的代码绑在C库分配器上 - 今天可能没事,明天可能会发现使用自定义分配器可能是一个更好的主意。
隐藏功能中的内存分配是不好的做法吗?
有时。
可以显示何时可以滥用代码的答案,以详细说明允许在内存分配中完全自由的函数的陷阱之一。
当功能本身确定所需的大小时,会发生经典案例,因此调用代码缺乏事先提供内存缓冲区所需的信息。
getline()
就是这种情况,其中流内容会影响分配的大小。尤其是在流为stdin
的情况下,问题的问题在于,对内存分配的控制是给予外部来源的,而不是受调用代码 - 程序的限制。外部输入可能会淹没内存空间 - hack。
使用修改后的功能,例如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)
执行内存分配的功能需要一定程度的控制,以防止具有特定参数或任务本质的无限内存分配。
c库函数避免返回分配的内存。这至少是strdup不属于标准库的一部分,以及一个流行的SCANF扩展程序,用于读取无限长度的C字符串。
您的库可以选择任何一种方式。使用预先分配的缓冲区更加灵活,因为它使用户可以通过静态分配的缓冲区通过。这种灵活性是有代价的,因为用户的代码变得更详细。
如果您选择动态为自定义结构分配内存,那么最好一旦用户不必要,就可以制作匹配功能来处理结构。