为什么C代码不返回结构?

虽然它非常方便,但我很少(如果有的话)遇到在C中返回struct (或union s)的函数,无论它们是动态链接函数还是静态定义函数。
它们通过指针参数返回数据。

(Windows中的动态示例是GetSystemInfo 。)

这背后的原因是什么?
是因为性能问题,ABI兼容性问题还是其他问题?

我会说“性能”,加上有时甚至有可能让C程序员感到惊讶。 它不是……在C的一般“味道”中,对于许多人来说,扔掉诸如结构之类的大事物就像它们仅仅是价值一样。 根据语言,它们确实是。

同样,许多C程序员似乎在需要复制结构时自动求助于memcpy() ,而不仅仅是使用赋值。

至少在C ++中,有一种称为“返回值优化”的东西能够以这种方式静默转换代码:

 struct Point { int x, y; }; struct Point point_new(int x, int y) { struct Point p; px = x; py = y; return p; } 

成:

 void point_new(struct Point *return_value, int x, int y) { struct Point p; px = x; py = y; *return_value = p; } 

它消除了结构值的(可能是堆栈饥饿的)“真实”返回。 我想更好的是这个,不确定他们是否那么聪明:

 void point_new(struct Point *return_value, int x, int y) { return_value->x = x; return_value->y = y; } 

我不确定C编译器是否可以执行任何此类操作,如果它们不能,那么我认为这可能是针对结构返回的真正参数,对于性能非常关键的程序。

原因主要是历史原因。 Rob Pike在他的论文“The Text Editor sam ”中写道

编程风格的相关问题: sam经常按值传递结构,这简化了代码。 传统上,C程序通过引用传递结构,但堆栈上的隐式分配更容易使用。 结构传递是C的一个相对较新的特性(它不在C 14的标准参考手册中),并且在大多数商业C编译器中得不到很好的支持。 但它方便且富有表现力,通过完全避免分配器并消除指针别名来简化内存管理。

话虽如此,该技术存在缺陷; 返回淫秽的大型结构机会堆栈溢出。

C中的返回是通过将返回值存储在堆栈中来实现的 。
返回结构或联合会导致在堆栈上放置可能非常大的数据,这可能导致堆栈溢出 。

仅返回指向struct / union的指针非常安全,因为您只在堆栈中放入少量数据(一般为4个字节)。

AC函数可以返回一个结构(C ++也是如此,它很常见)。 也许在C的最初几年,它不可能。

适用于Linux和相关系统的x86-64 ABI规范 (第21页)甚至表示,适合两个-64位字的结构通常可以在两个寄存器中返回,而无需通过内存(甚至是堆栈)。 很可能这比通过堆栈更快。

正如unwind在他的回答中所回答的那样 ,ABI经常要求将结构结果静默地转换为不可见的指针。

甚至可以定义另一个调用约定,它在更多寄存器中返回更多数据。 但是这样的新约定会破坏所有目标代码并需要重新编译所有内容(甚至包括Linux上的libc.so.6等系统库),当然还需要更改编译器。

当然,ABI约定与处理器,系统和编译器有关。

我不知道Windows,我不知道Windows定义为什么是ABI。

在ANSI-C之前,您无法返回结构类型的对象,也无法传递结构类型的参数。

来自Chris Torek在comp.lang.c中的引用:

请注意,V6 C也不支持结构值参数和结构值返回值。

现在它们不是很常用的原因是人们更喜欢返回指向结构的指针,该结构只涉及指针副本而不是整个结构对象的副本。

除了可能存在性能损失或者在标准前几天内通常不支持按值返回结构的想法之外,C函数不使用结构值的返回值的另一个原因是,如果返回struct你不能轻易返回成功/失败指标。 我知道我偶尔会开始设计一个函数来返回在函数中初始化的结构,但是后来我遇到了如何指示函数是否成功的问题。 你几乎有以下选择:

  • 保证成功(有时这是可能的)
  • 传入指向错误代码位置的指针
  • 在结构中有一个字段或标记值,表示成功/失败

只有选项1才能使界面不再是一个kludge。 第二种选择类型失败了按值返回结构的目的,实际上使得函数更难以用于处理失败。 第三种选择在几乎所有情况下都显然不是一个好的设计。

通常,Windows函数不返回任何内容或错误代码,特别是在返回结构或类时。

效率可能是一个问题,尽管RVO 应该消除开销。

我认为主要原因是保持方法与之前使用的编码风格内联。