繁体   English   中英

返回分配的缓冲区,与传递给 function 的缓冲区

[英]Returning allocated buffer, vs buffer passed to a function

将值传递给我的函数时,我经常考虑从 function 返回分配的缓冲区,而不是让 function 将缓冲区作为参数。 我试图弄清楚将缓冲区传递给我的 function 是否有任何重大好处(例如:

void f(char **buff) {
   /* operations */
   strcpy(*buff, value);
}

相对

char *f() {
    char *buff = malloc(BUF_SIZE);
    /* operations */
    return buff;
}

这些显然不是超级高级的例子,但我认为这一点是正确的。 但是,是的,让用户传递分配的缓冲区有什么好处,还是返回分配的缓冲区更好?

使用其中一个有什么好处,还是只是没用?

这是 function 是否应通过其返回值或通过 out 参数将数据返回给其调用者的更一般问题的特定情况。 两种方法都可以正常工作,优缺点主要是风格上的,而不是技术上的。

主要的技术考虑是每个function只有一个返回值,但可以有任意数量的out参数。 这可以解决,但这样做可能是不可接受的。 例如,如果您想保留函数的返回值以用作许多标准库函数产生的状态代码,那么这会限制您发送回其他数据的选项。

一些风格上的考虑是

  • 使用返回值更符合数学 function 的习语;
  • 许多人难以理解指针; 特别是,
  • 通过指针进行的非局部修改有时会让人感到困惑。 另一方面,
  • function 的返回值可以直接在表达式中使用。

关于自最初发布此答案以来对该问题的修改,如果问题是关于是否动态分配和填充新的 object填充调用者提出的 object ,那么还有这些额外的注意事项:

  • 在 function 中分配 object 可以使调用者不必自己分配它,这很方便。 另一方面,
  • 在 function 内部分配 object可防止调用者自己分配它(可能是自动或静态),并且不提供重新初始化现有 ZA8CFDE6331BD59EB2AC96F8911C4B66 还,
  • 返回指向已分配 object 的指针会掩盖调用者有义务释放它的事实。

当然,你可以同时拥有它:

void init_thing(thing *t, char *name) {
    t->name = name;
}

thing *create_thing(char *name) {
    thing *t = new malloc(sizeof(*t));

    if (t) {
        init_thing(t);
    }
    return t;
}

两种选择都有效。
但一般情况下,通过参数返回信息(第二种方式)更可取,因为我们通常保留function的返回报错。 我们可以通过多个参数返回多个信息。 因此,调用者更容易通过首先检查返回值来检查 function 是否正常。 C 库或 Linux 系统调用中的大多数服务都是这样工作的。

关于您的示例,这两个选项都有效,因为您正在引用一个在程序加载时全局分配的常量字符串。 因此,在这两种解决方案中,您都返回此字符串的地址。
但是,如果您执行以下操作:

char *func(void) {
   char buff[] = "example";
   return buff;
}

您实际上将常量字符串“example”的内容复制到buff指向的 function 的堆栈区域中。 在调用者中,返回的地址不再有效,因为它指的是一个堆栈位置,可以被调用者调用的任何其他 function 重用。
让我们使用这个 function 编译一个程序:

#include <stdio.h>

char *func(void) {
   char buff[] = "example";
   return buff;
}

int main(void) {

  char *p = func();

  printf("%s\n", p);

  return 0; 

}

如果编译器的编译选项足够聪明,我们会得到第一个带有警告的危险信号,如下所示:

$ gcc -g bad.c -o bad
bad.c: In function 'func':
bad.c:5:11: warning: function returns address of local variable [-Wreturn-local-addr]
    5 |    return buff;
      |           ^~~~

编译器指出func()正在返回其堆栈中的本地空间地址,当 function 返回时,该地址不再有效。 这是触发此警告的编译器选项-Wreturn-local-addr 让我们停用此选项以删除警告:

$ gcc -g bad.c -o bad -Wno-return-local-addr

所以,现在我们有一个编译为 0 警告的程序,但这会产生误导,因为执行失败或可能触发一些不可预测的行为:

$ ./bad
Segmentation fault (core dumped)

不能返回本地 memory 的地址。

您的第一个示例有效,因为"example"中的 memory 不会被释放。 但是,如果您分配了本地(又名自动) memory 它会在 function 返回时自动被释放; 返回的指针将无效。

char *func() {
   char buff[10];

   // Copy into local memory
   strcpy(buff, "example");

   // buff will be deallocated after returning.
   // warning: function returns address of local variable
   return buff;
}

您可以使用malloc返回动态 memory ,然后调用者必须free

char *func() {
  char *buf = malloc(10);
  strcpy(buff, "example");
  return buff;
}

int main() {
  char *buf = func();
  puts(buf);
  free(buf);
}

或者你让调用者分配 memory 并传入。

void *func(char **buff) {
   // Copy a string into local memory
   strcpy(buff, "example");

   // buff will be deallocated after returning.
   // warning: function returns address of local variable
   return buff;
}

int main() {
  char buf[10];
  func(&buf);
  puts(buf);
}

好处是调用者可以完全控制 memory。 他们可以重用现有的 memory,也可以使用本地的 memory。

缺点是调用者必须分配正确数量的 memory。 这可能会导致分配过多的 memory,也可能导致分配过少。

另一个缺点是 function 无法控制已传入的 memory。它无法增长、收缩或释放 memory。

您只能从 function 返回一件事。

例如,如果要将字符串转换为 integer,则可以像atoi一样返回 integer。 int atoi( const char *str )

int num = atoi("42");

但是当转换失败时会发生什么? atoi返回0 ,但是如何区分atoi("0")atoi("purple")

您可以改为为转换后的值传入一个int * int my_atoi( const char *str, int *ret )

int num;
int err = my_atoi("42", &num);
if(err) {
  exit(1);
}
else {
  printf("%d\n");
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM