C:何时返回值或传递引用

12

虽然这个话题已经被讨论了很多次,但我迄今还没有找到令人满意的答案。何时通过return返回函数中的数据或传递引用来更改地址上的数据?经典答案是在变量变得很大时(为了避免堆栈复制),将变量作为引用传递给函数。对于任何结构或数组等任何东西似乎都是这样。然而,从函数返回指针并不罕见。事实上,一些C库函数就是这样做的。例如:

char *strcat(char *dst, const char *src);

即使在出现错误的情况下,此函数始终返回指向目标的指针。在这种情况下,我们可以只使用传递的变量,不用管返回值(大多数情况下是这样做的)。

当查看结构时,我看到同样的情况发生。当函数需要在变量初始化中使用时,我经常返回指针。

char *p = func(int i, const char *s);

还有一个观点认为堆栈拷贝变量是昂贵的,应该使用指针代替。但是正如 这里 提到的那样,有些编译器可以自行决定(假设C语言也一样)。是否存在一般规则或至少有什么不成文的约定来决定使用其中之一?我重视性能胜过设计。


3
听起来更像是一个 http://programmers.stackexchange.com/ 的问题。 - cup
返回指针通常会导致对象的生命周期管理问题,因为90%的情况下,被指向的对象是由函数新构造的。 - user3528438
strcat的例子适用于旧的API。新的替代方式返回错误代码:errno_t strcat_s( char *strDestination, size_t numberOfElements, const char *strSource ) - i486
@i486 - strcat_s()(以及其他 _s 函数)是否被普遍支持? - ryyker
据我所知,那是Windows API。glibc目前还没有安全函数。 - Yorick de Wid
@YorickdeWid - 是的,我在我的评论中有点含蓄。我的意思是指出i486的评论似乎偏袒了一种不被普遍接受的C实现。 - ryyker
2个回答

14

首先,从逻辑层面决定哪种方法最合理,不考虑性能影响。如果按值返回struct最清楚地传达了代码的意图,那就这么做。

现在不是80年代了。编译器自那时以来已经变得更加智能化,可以非常好地优化代码,特别是写得清晰简单的代码。类似地,参数传递和值返回约定也变得相当复杂。 简单的基于堆栈的模型并不真正反映现代硬件的现实情况。

如果生成的应用程序不符合您的性能要求,则通过分析器运行它以查找瓶颈。如果发现按值返回该struct引起问题,那么您可以尝试将其作为引用传递到函数中进行实验。

除非您正在使用高度受限制的嵌入式环境,否则您无需计算每个字节和CPU周期。您不想过度浪费,但同样,您也不想过于关注低级细节,除非a)您有非常严格的性能要求,并且b)您对您特定平台的细节非常熟悉(这意味着您不仅彻底了解您平台的函数调用约定,还知道编译器如何使用这些约定)。否则,您只是在猜测。让编译器为您做苦工。那是它存在的原因。


-2

经验法则:

  1. 如果sizeof(return type)sizeof(int)大,您应该通过指针传递它以避免复制开销。这是一个性能问题。解引用指针会有一些代价,所以这个规则也有一些例外。
  2. 如果返回类型很复杂(包含指针成员),请通过指针传递。将局部返回值复制到堆栈上不会复制动态内存,例如。
  3. 如果您想让函数分配内存,则应返回指向新分配的内存的指针。这被称为工厂设计模式。
  4. 如果您想从函数返回多个值,请通过值返回其中一个,并通过指针传递其余内容。
  5. 如果您有一个既是输入又是输出的复杂/大型数据类型,请通过指针传递。

1
你们的“经验法则”是从哪里来的?能否提供参考链接? - chain ro
我的经验、一些常识和良好的行业惯例。 - liorda
1
我会选择 sizeof(return) < sizeof(int*)。此外,如果返回类型的复制构造函数/赋值运算符等执行非标准操作,即使类大小很小,也值得通过引用进行处理。我见过一些初始化网络连接的复制构造函数。人们在奇怪的地方放置了一些疯狂的东西。 - LawfulEvil
@Pandrei 关于第5点 - 如果这是一个POD,通过指针传递它并没有太多意义。除非符合规定之一,你应该始终将结果作为返回类型获取。 - liorda
@liorda,我知道POD是什么,但在C的上下文中,这个区别完全没有意义。这是C++的术语,而不是C。 - Jens Gustedt
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接