在我使用 C 编码的经验中,我看到了两种为函数传递参数的方法:
malloc
在调用函数之前malloc
内部函数(变量在调用函数之前未初始化)
我特别喜欢第二种形式。但是,虽然我是唯一一个编写程序的人,但我知道这一点,但其他人不知道,并且可能导致 2malloc
和内存泄漏。
所以,我的问题是:最好的做法是什么?
在调用者中分配内存更加灵活,因为它允许调用者使用静态或自动存储而不是动态分配,并且无需处理被调用者分配失败的情况。另一方面,让调用者提供存储需要调用者提前知道大小。如果将大小作为常量编译到调用者中,并且被调用者位于稍后更新为使用更大结构的库中,那么事情将严重中断。当然,您可以通过提供第二个函数(或库中的外部变量)来检索必要的大小来避免这种情况。
如有疑问,您始终可以使用两个功能:
然后调用者可以自由选择更适合特定用例的方法。
我个人强烈赞成您的第一个正交性命题(只要可能)。举个例子:
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);
}
这是正交的。malloc
并且free
在干净且可读的相同词法范围内调用。另一个优点是您可以将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 库中重复出现。
要求调用者分配和释放内存至少有两个缺点: