在使用C代码的过程中,我看到了两种传递函数参数的方法:
调用函数之前的malloc
函数内部的malloc
(变量在调用函数之前未初始化)
我特别喜欢第二种形式。虽然我是唯一一个编写程序的人,但我知道这一点,但其他人不知道,这可能会导致2malloc
和内存泄漏。
所以,我的问题是:这方面的最佳实践是什么?
在调用者中分配内存更灵活,因为它允许调用者使用静态或自动存储,而不是动态分配,并且无需处理被调用者中分配失败的情况。另一方面,让呼叫者提供存储需要呼叫者事先知道大小。如果将大小作为常量编译到调用者中,并且被调用者所在的库稍后会更新为使用更大的结构,那么情况就会严重恶化。当然,您可以通过提供第二个函数(或库中的外部变量)来检索必要的大小来避免这种情况。
当你有疑问时,你总是可以做两个功能:
- 使用调用方提供的存储的主函数
- 一个包装器函数,它分配适当的存储量,使用它调用#1中的函数,并将指针返回给调用者
然后调用方可以自由选择更适合特定用例的方法。
我个人非常赞成你的第一个正交性命题(只要可能)。举以下例子:
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);
}
这是正交的。CCD_ 4和CCD_。另一个优点是可以将具有不同存储持续时间的数组传递给bar
函数,例如具有自动或静态存储持续时间。您让bar
函数只关注它所做的工作,并让另一个函数管理数组分配。
请注意,这也是所有标准C函数的工作方式:它们从不出现来调用malloc
。
我用来决定的标准是:
-
如果被调用函数之外的代码可以知道要分配多少内存,那么最好让调用代码分配内存。
-
如果被调用函数外的代码不知道要分配多少内存,那么被调用函数必须进行内存分配。那么,很可能会有第二个函数可用于释放第一个函数返回的内存("被调用"的函数),除非它只是需要一个
free()
。功能文档应明确这一点。
例如,如果被调用的函数正在从文件中读取完整的树结构,则该函数必须分配内存。但是,还会有一个用于释放内存的配套函数(因为被调用的代码知道如何释放内存,而调用的代码不需要知道)。
另一方面,如果被调用函数正在将一个简单的整数和浮点值列表读取到一个固定大小的结构中,那么让调用函数分配内存要好得多。请注意,我跳过了"字符串"!如果字符串在结构中具有固定大小,则调用函数可以进行分配,但如果字符串具有可变大小,则可能由被调用函数进行分配。
标准C库具有类似fgets()
的函数,这些函数期望调用代码分配要使用的内存。调用序列告诉fgets()
有多少空间可用。如果你没有提供足够的内存,你就会遇到问题。(fgets()
的问题是,你可能只得到一行文本的开头,而不是整行文本。)
POSIX 2008库提供了getline()
,它将为该行分配足够的空间。
asprintf()
和相关功能(见TR24731-2)根据需要分配内存。snprintf()
函数不会——它会被告知有多少可用空间,它只使用了这个空间,并说明它真正需要多少空间,如果你没有提供足够的空间并对此采取措施(分配更多的空间并重试,或者愉快地忽略截断的值,然后继续,就好像没有出问题一样),则由你来注意。
信息隐藏的原理表明,分配内存最好在函数中完成。
如果您了解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库中重复出现。
要求调用者分配和释放内存至少有两个缺点:
- 调用端需要额外的代码
- 如果分配的结构的大小发生了变化,则调用代码将需要重新编译(至少)或更改