为什么大多数编程语言仅支持从函数返回单个值?[关闭]


118

为什么大多数(?)编程语言中的函数都设计为支持任意数量的输入参数,但仅支持一个返回值?

在大多数语言中,可以通过使用输出参数,返回指针或定义/返回结构/类来“解决”该限制。但是,编程语言没有设计为以更“自然”的方式支持多个返回值似乎很奇怪。

请问对此有解释吗?


40
因为你可以返回一个数组...
nathan hayfield

6
那么,为什么不只允许一个论点呢?我怀疑它与语音相关。采取一些想法,将他们塞进一件事,然后您回到足够幸运/不幸的地方去听。回报几乎就像一种意见。“这”就是您对“这些”的处理方式。
Erik Reppen

30
我相信python的方法非常简单优雅:当您必须返回多个值时,只需返回一个元组:def f(): return (1,2,3)然后可以使用元组拆包来“拆分”该元组:a,b,c = f() #a=1,b=2,c=3。无需创建数组和手动提取元素,无需定义任何新类。
巴库里

7
我可以告知您Matlab的返回值数量可变。的输出参数的数量由调用签名(例如确定[a, b] = f()[a, b, c] = f())并获得内部f通过nargout。我不是Matlab的忠实拥护者,但有时确实很方便。
gerrit 2013年

5
我认为,如果大多数编程语言都以这种方式设计,则值得商bat。在编程语言的历史上,曾经有一些非常流行的语言(例如Pascal,C,C ++,Java,经典VB)创建,但是今天,还有许多其他语言也吸引了越来越多的支持多重返回的支持者价值观。
布朗

Answers:


58

某些语言(如Python)本机支持多个返回值,而某些语言(如C#)则通过其基础库支持它们。

但是总的来说,即使在支持它们的语言中,也很少使用多个返回值,因为它们草率:

  • 返回多个值的函数很难清楚地命名
  • 容易误认为返回值的顺序

    (password, username) = GetUsernameAndPassword()  
    

    (出于同样的原因,许多人避免对一个函数使用过多的参数;甚至有人甚至认为一个函数不应具有两个相同类型的参数!)

  • OOP语言已经可以更好地替代多个返回值: 类。
    它们的类型更强,它们将返回值分组为一个逻辑单元,并且使返回值(属性)的名称在所有用途中保持一致。

的一处,他们相当方便是在语言(如Python),其中从一个函数的多个返回值可以被用作多个输入参数到另一个。但是,这是比使用类更好的设计的用例。


50
很难说返回元组就是返回多个事物。它返回一个元组。您编写的代码只是使用一些语法糖将其干净地打包了。

10
@Lego:我看不出区别-元组在定义上是多个值。如果不是这样,您将如何看待“多个返回值”?
BlueRaja-Danny Pflughoeft13年

19
这是一个非常朦胧的区别,但是请考虑使用一个空的Tuple ()。那是一件事还是零件事?就个人而言,我会说一件事。我可以分配x = ()就好,就像我可以分配一样x = randomTuple()。在后者中,如果返回的元组为空,我仍然可以将一个返回的元组分配给x

19
...我从未声称元组不能用于其他用途。但是他们认为“Python不支持多个返回值,它支持元组”仅仅是被极其无谓迂腐。这仍然是正确的答案。
BlueRaja-Danny Pflughoeft13年

14
元组和类都不是“多个值”。
Andres F.

54

因为函数是执行计算并返回结果的数学构造。确实,很少有几种编程语言在“幕后”上仅着眼于一个输入和一个输出,而多个输入只是对输入的一薄包装-当单个值输出不起作用时,使用单个内聚结构(或元组或Maybe)是输出(尽管“单个”返回值由许多值组成)。

这没有改变,因为程序员发现out参数是笨拙的构造,仅在有限的一组场景中有用。像许多其他事情一样,由于需求/需求不存在,因此也没有支持。


5
@FrustratedWithFormsDesigner-这在最近的问题中有点出现。一方面,我可以指望20年内我想要多个输出的次数。
Telastyn

61
数学函数和大多数编程语言函数是两种截然不同的野兽。
tdammers

17
@tdammers在早期,他们的想法非常相似。Fortran,pascal等对计算的影响比对计算体系结构的影响更大。

9
@tdammers-怎么回事?我的意思是,对于大多数语言而言,最终归结为lambda演算-一个输入,一个输出,没有副作用。除此之外,其他一切都是模拟/黑客。从某种意义上说,编程功能可能不是纯粹的,多个输入可能会产生相同的输出,但实质是存在的。
Telastyn

16
@SteveEvers:不幸的是,“功能”这个名称接管了命令式编程,而不是更合适的“过程”或“例程”。在函数式编程中,函数与数学函数非常相似。
tdammers

35

在数学中,“定义明确”的函数是给定输入只有1个输出的函数(请注意,您只能使用单个输入函数,并且在语义上仍使用currying获得多个输入)。

对于多值函数(例如,正整数的平方根),返回一个集合或值序列就足够了。

对于您正在谈论的函数类型(即,返回多个值,不同类型的函数),我所看到的与您似乎略有不同:我认为需要/使用out参数是一种更好的设计或解决方案。更有用的数据结构。例如,我更希望*.TryParse(...)方法返回一个Maybe<T>monad而不是使用out参数。考虑一下F#中的以下代码:

let s = "1"
match tryParse s with
| Some(i) -> // do whatever with i
| None -> // failed to parse

对于这些构造,编译器/ IDE /分析支持非常好。这将解决大部分参数的“需求”。老实说,我无法想到其他任何方法都不是解决方案。

对于其他场景(我不记得了),一个简单的Tuple就足够了。


1
另外,我真的很想能够用C#编写:var (value, success) = ParseInt("foo");由于(int, bool) ParseInt(string s) { }已声明,因此将进行编译时类型检查。我知道可以使用泛型来完成此操作,但仍然可以为添加更好的语言做准备。
绝望的鬼脸

10
@GrimaceofDespair您真正想要的是解构语法,而不是多个返回值。
Domenic

2
@沃伦:是的。请参阅此处,请注意,这样的解决方案将不会是连续的:en.wikipedia.org/wiki/Well-definition
史蒂文·埃弗斯

4
定义明确的数学概念与函数返回的输出数量无关。这意味着,如果输入相同,则输出将始终相同。严格来说,数学函数返回一个值,但该值通常是一个元组。对于数学家来说,此值与返回多个值之间基本上没有区别。关于编程函数应该仅返回一个值的争论,因为数学函数的作用不是很强。
迈克尔·席勒

1
@MichaelSiler(我同意您的意见),但请注意,该参数是可逆的:“程序功能可以返回多个值的参数,因为它们可以返回单个元组值,也不太引人注目” :)
Andres F.

23

除了当您查看函数返回时在汇编中使用的范例时已经说过的内容之外,它还会在特定寄存器中留下指向返回对象的指针。如果他们使用变量/多个寄存器,则该函数在库中时,调用函数将不知道从何处获取返回值。因此,这将使链接到库变得困难,并且与其设置任意数量的可返回指针,不如使用它们。高级语言并没有完全相同的借口。


啊! 非常有趣的细节。+1!
史蒂文·埃弗斯

这应该是公认的答案。通常,人们在构建编译器时会考虑目标计算机。另一个类比是为什么我们要有int,float,char / string等,因为这是目标计算机所支持的。即使目标不是裸机(例如jvm),您仍然希望通过不进行过多仿真来获得不错的性能。
imel96

26
...您可以轻松地定义用于从函数返回多个值的调用约定,几乎可以像定义用于将多个值传递给函数的调用约定那样。这不是答案。-1
BlueRaja-Danny Pflughoeft13年

2
知道基于堆栈的执行引擎(JVM,CLR)是否曾经考虑过/允许多个返回值将是很有趣的。调用方只需弹出正确数量的值,就像它推入正确数量的参数一样!
罗伦佐·德玛特(LorenzoDematté),

1
@David不,cdecl(理论上)允许无限数量的参数(这就是为什么可以使用varargs函数的原因)。尽管某些C编译器可能会将每个函数的参数限制为数十或数百个参数,但我认为这仍然不合理-_-
BlueRaja-Danny Pflughoeft

18

现代语言功能不再需要过去使用多个返回值的许多用例。要返回错误代码?引发异常或返回Either<T, Throwable>。是否要返回可选结果?返回Option<T>。是否要返回以下几种类型之一?返回一个Either<T1, T2>或已标记的联合。

即使在您确实需要返回多个值的情况下,现代语言通常也支持元组或某种数据结构(列表,数组,字典)或对象,以及某些形式的解构绑定或模式匹配,这使得打包您将多个值合并为一个值,然后将其再次分解为多个琐碎的值。

以下是一些支持返回多个值的语言的示例。我真的看不到增加对多个返回值的支持如何使它们显着地表现出来以抵消新语言功能的成本。

红宝石

def foo; return 1, 2, 3 end

one, two, three = foo

one
# => 1

three
# => 3

蟒蛇

def foo(): return 1, 2, 3

one, two, three = foo()

one
# >>> 1

three
# >>> 3

斯卡拉

def foo = (1, 2, 3)

val (one, two, three) = foo
// => one:   Int = 1
// => two:   Int = 2
// => three: Int = 3

哈斯克尔

let foo = (1, 2, 3)

let (one, two, three) = foo

one
-- > 1

three
-- > 3

Perl6

sub foo { 1, 2, 3 }

my ($one, $two, $three) = foo

$one
# > 1

$three
# > 3

1
我认为一个方面是,在某些语言(例如Matlab)中,函数可以灵活地返回多少个值。见上面我的评论。我不喜欢Matlab中的许多方面,但这是我从Matlab移植到例如Python时缺少的少数几个功能之一(也许是唯一的)。
Gerrit

1
但是像Python或Ruby这样的动态语言呢?假设我写了类似Matlab的sort函数:sorted = sort(array)仅返回排序后的数组,而同时[sorted, indices] = sort(array)返回两者。我在Python中想到的唯一方法是sort沿sort(array, nout=2)或传递标记sort(array, indices=True)
gerrit

2
@MikeCellini我不这么认为。一个函数可以告诉该函数有多少个输出参数,[a, b, c] = func(some, thing)并据此采取行动。例如,如果计算第一个输出参数很便宜但是计算第二个参数很昂贵,这很有用。我不熟悉在nargout运行时可以使用等效的Matlab的任何其他语言。
gerrit

1
@gerrit在Python中正确的解决方案是这样写:sorted, _ = sort(array)
Miles Rout 2014年

1
@MilesRout:sort函数可以告诉您它不需要计算索引吗?太酷了,我不知道。
约尔格W¯¯米塔格

12

单个返回值如此受欢迎的真正原因是在许多语言中使用的表达式。在任何可以使用表达式的语言中,您x + 1都已经在考虑单个返回值,因为您可以通过将表达式分解成碎片并确定每个表达式的值来评估表达式。您查看x并确定其值为3(例如),然后查看1,然后查看x + 1并合计起来确定整体的值为4。表达式的每个句法部分都有一个值,没有其他数量的值;这是每个人都期望的表达式的自然语义。即使一个函数返回一对值,它实际上仍在返回一个正在执行两个值的值,因为函数返回两个未以某种方式包装到单个集合中的值的想法太奇怪了。

人们不想处理使函数返回多个值所需的替代语义。例如,在像Forth这样的基于堆栈的语言中,您可以具有任意数量的返回值,因为每个函数仅修改堆栈的顶部,弹出输入并随意推送输出。这就是为什么Forth没有普通语言所具有的那种表达方式的原因。

Perl是另一种语言,有时可以像函数一样返回多个值,尽管通常只考虑返回一个列表。Perl 中的“内插”列表方式为我们提供了这样的列表,就像(1, foo(), 3)大多数不了解Perl的人所希望的那样,其中可能包含3个元素,但也可能仅包含2个元素,4个元素或更多的元素,具体取决于foo()。Perl中的列表是扁平的,因此语法列表并不总是具有列表的语义。它可能只是更大列表的一部分。

使函数返回多个值的另一种方式是具有替代的表达式语义,其中任何表达式都可以具有多个值,并且每个值都代表一种可能性。请x + 1再试一次,但是这次假设x有两个值{3,4},则的值x + 1将为{4,5},并且的值x + x将为{6,8},或者可能是{6,7,8} ,取决于是否允许一个评估使用多个值x。类似于Prolog用来为查询提供多个答案的语言,可以使用回溯来实现。

简而言之,函数调用是单个语法单元,并且单个语法单元在我们都知道并喜欢的表达语义中具有单个值。任何其他语义都会迫使您采用奇怪的处理方式,例如Perl,Prolog或Forth。


9

正如该答案所建议的,这是硬件支持的问题,尽管语言设计中的传统也起着作用。

当函数返回时,它将指针指向特定寄存器中的返回对象

在Fortran,Lisp和COBOL这三种第一语言中,第一种使用的返回值是基于数学建模的。第二个返回任意数量的参数,就像接收它们一样:作为一个列表(也可以说它仅传递并返回一个参数:列表的地址)。第三个返回零或一个值。

这些最早的语言对跟随它们的语言的设计产生了很大的影响,尽管唯一返回多个值的语言Lisp从未受到广泛的欢迎。

当C语言问世时,虽然受到了之前的语言的影响,但它非常注重有效利用硬件资源,并在C语言所做的工作与实现它的机器代码之间保持着紧密的联系。它的一些最古老的功能,例如“自动”与“注册”变量,是这种设计理念的结果。

还必须指出的是,汇编语言直到80年代才开始广泛流行,直到80年代才逐渐被主流开发所淘汰。编写编译器和创建语言的人都熟悉汇编语言,并且在大多数情况下,保持最佳状态。

与该规范背道而驰的大多数语言都从未受到太大欢迎,因此也从未在影响语言设计师的决策方面发挥过强大的作用(当然,他们的灵感来自于他们所知道的)。

因此,让我们来看一下汇编语言。首先让我们看一下6502,这是1975年的微处理器,在Apple II和VIC-20微型计算机中广为使用。与当时的大型机和小型计算机相比,它非常脆弱,尽管与20年前30年代编程语言兴起时的第一台计算机相比,它具有强大的功能。

如果您看一下技术说明,它有5个寄存器和几个1位标志。唯一的“满”寄存器是程序计数器(PC)-该寄存器指向要执行的下一条指令。其他寄存器是累加器(A),两个“索引”寄存器(X和Y)以及堆栈指针(SP)。

调用子例程会将PC放入SP指向的内存中,然后递减SP。从子例程返回则相反。一个人可以在栈中推入和拉出其他值,但是相对于SP而言,它很难引用到内存,因此编写重入子例程非常困难。我们认为这是理所当然的,它在我们认为随时都可以调用子例程的情况下,在这种体系结构上并不常见。通常,将创建一个单独的“堆栈”,以便将参数和子例程返回地址保持分开。

如果您看一下激发6502的处理器6800,它还有一个与SP一样宽的寄存器,即索引寄存器(IX),可以从SP接收值。

在机器上,调用重入子例程包括将参数压入堆栈,压入PC,将PC更改为新地址,然后该子例程会将其局部变量压入堆栈。由于局部变量和参数的数量是已知的,因此可以相对于堆栈进行寻址。例如,一个接收两个参数并具有两个局部变量的函数如下所示:

SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1

可以调用任意次,因为所有临时空间都在堆栈上。

在TRS-80和许多基于CP / M的微型计算机上使用的8080,可以通过将SP压入堆栈,然后将其弹出到其间接寄存器HL中来实现与6800类似的功能。

这是一种非常常见的实现方式,它在更现代的处理器上获得了更多的支持,它的基本指针使返回之前的所有局部变量都易于转储。

问题是,您如何退还任何东西?处理器寄存器在早期并不是很多,经常需要使用其中一些来找出要寻址的内存。返回堆栈上的东西会很复杂:您必须弹出所有内容,保存PC,然后推回返回的参数(该参数将同时存储在哪里?),然后再次推PC并返回。

因此,通常要做的是为返回值保留一个寄存器。调用代码知道返回值将在特定的寄存器中,必须保留该值,直到可以保存或使用它为止。

让我们看一下一种允许多个返回值的语言:Forth。Forth所做的是保持单独的返回堆栈(RP)和数据堆栈(SP),以便函数必须要做的就是弹出其所有参数,并将返回值保留在堆栈上。由于返回堆栈是单独的,因此不会妨碍您的操作。

作为在使用计算机的前六个月中学习汇编语言和Forth的人,多个返回值对我来说似乎完全正常。像Forth这样的运算符/mod返回整数除法其余运算符似乎很明显。另一方面,我可以很容易地看到一个有C思维的早期经验的人如何发现这个概念很奇怪:它违背了他们对“功能”的根深蒂固的期望。

至于数学……好吧,在我上数学课之前,我正在用计算机编程。还有就是 CS和由数学影响编程语言的一个整体部分,但是,话又说回来,有一整节是不是。

因此,在数学影响早期语言设计,硬件限制决定了易于实现的因素以及流行语言影响硬件演变方式的因素方面,我们综合了一些因素(Lisp机器和Forth机器处理器是此过程中的杀手kill)。


@gnat故意使用“提供基本质量”中的“告知”。
Daniel C. Sobral

如果对此有强烈的想法,请随时回滚;根据我的阅读,这里的影响力稍好一些:“以一种重要的方式影响……”
gnat 2013年

1
+1指出,与大量现代CPU寄存器组(也用于许多ABI,例如x64 abi)相比,早期CPU的寄存器稀疏计数可能会改变游戏规则,并且以前令人信服的原因仅返回1值如今可能只是历史原因。
BitTickler '16

我不相信早期的8位微型CPU对语言设计以及在任何体系结构中的C或Fortran调用约定中假定需要做什么有很大影响。Fortran假定您可以传递数组args(本质上是指针)。因此,由于缺少指针+索引的寻址模式,因此在诸如6502之类的机器上,对于普通的Fortran来说,已经存在主要的实现问题,就像您的答案以及“ 为什么C到Z80编译器会产生不良代码?”中所讨论的那样在逆向计算上
彼得·科德斯

像C一样,Fortran也假定您可以传递任意数量的args,并且可以随机访问它们和任意数量的本地变量,对吗?正如您刚刚解释的那样,在6502上您容易做到这一点,因为相对于堆栈的寻址不是问题,除非您放弃重入。您可以将它们弹出到静态存储中。如果可以传递任意的arg列表,则可以为寄存器中不适合的返回值(例如,超出第一个)添加额外的隐藏参数。
彼得·科德斯

7

我知道的功能语言可以通过使用元组轻松返回多个值(在动态类型的语言中,甚至可以使用列表)。其他语言也支持元组:

f :: Int -> (Int, Int)
f x = (x - 1, x + 1)

// Even C++ have tuples - see Boost.Graph for use
std::pair<int, int> f(int x) {
  return std::make_pair(x - 1, x + 1);
}

在上面的示例中,f函数返回2个整数。

同样,ML,Haskell,F#等也可以返回数据结构(对于大多数语言而言,指针的级别太低)。我还没有听说过现代GP语言有这样的限制:

data MyValue = MyValue Int Int

g :: Int -> MyValue
g x = MyValue (x - 1, x + 1)

最后,out即使使用功能语言,也可以使用来模拟参数IORef。在大多数语言中,没有本地支持out变量的原因有几个:

  • 语义不明确:以下函数是否打印0或1?我知道可以打印0的语言和可以打印1的语言。这两种语言都有好处(在性能方面,以及与程序员的思维模型相匹配):

    int x;
    
    int f(out int y) {
      x = 0;
      y = 1;
      printf("%d\n", x);
    }
    f(out x);
    
  • 非本地化的效果:如上例所示,您可以发现您的链条很长,并且最里面的函数会影响全局状态。通常,很难确定功能的要求以及更改是否合法。鉴于大多数现代范例都试图定位效果(封装在OOP中)或消除副作用(功能编程),因此它与这些范例冲突。

  • 是多余的:如果有元组,则您有99%的out参数功能和100%的惯用用法。如果将指针添加到混合,则覆盖剩余的1%。

我很难命名一种语言,该语言不能通过使用元组,类或out参数来返回多个值(并且在大多数情况下,允许使用两个或多个这些方法)。


+1提及功能语言如何优雅而轻松地处理此问题。
Andres F.

1
从技术上讲,您仍在返回单个值still:D(只是将这个单个值分解为多个值是微不足道的)。
Thomas Eding

1
我要说的是,具有真正“输出”语义的参数应充当编译器临时变量,当方法正常退出时,该临时变量将被复制到目标位置。语义变量为“ inout”的变量应充当编译器临时变量,该变量在入口时从传入的变量加载,并在退出时写回;语义为“ ref”的人应该充当别名。C#的所谓“ out”参数实际上是“ ref”参数,因此具有这种行为。
2014年

1
元组“解决方法”也不是免费提供的。它阻止了优化机会。如果存在一个允许在CPU寄存器中返回N个返回值的ABI,则编译器实际上可以进行优化,而不是创建一个元组实例并对其进行构造。
BitTickler '16

1
@BitTickler如果您控制ABI,没有什么可以阻止返回结构的前n个字段通过寄存器传递的。
Maciej Piechotka '16

6

我认为这是因为诸如的表达式(a + b[i]) * c

表达式由“奇异”值组成。因此,可以将返回奇异值的函数直接用于表达式中,以代替上面显示的四个变量中的任何一个。多输出函数在表达式中至少有些笨拙。

我个人觉得,这是这特殊的有关同一个返回值的东西。您可以通过添加语法来解决此问题,该语法用于指定要在表达式中使用的多个返回值中的哪一个,但它肯定比老式的数学符号更笨拙,后者对于每个人来说都是简洁明了的。


4

它确实使语法复杂了一点,但是在实现级别没有充分的理由不允许这样做。与某些其他响应相反,在可用时返回多个值会导致代码更清晰,更高效。我无法计数我希望多久返回一次X Y,或者返回“成功”布尔值有用的值。


3
您能否提供一个示例,其中多个退货提供更清晰和/或更有效的代码?
史蒂文·埃弗斯

3
例如,在C ++ COM编程中,许多函数都有一个[out]参数,但实际上所有函数都返回一个HRESULT(错误代码)。只在那里买一双将是相当实际的。在诸如Python之类的对元组有很好支持的语言中,我见过的许多代码都使用了这种语言。
Felix Dombek

在某些语言中,您将返回带有X和Y坐标的向量,并且返回任何有用的值都将被视为“成功”,但有可能带有该有用值的异常会被用于失败。
doppelgreener

3
很多时候,您最终都以非显而易见的方式将信息编码为返回值-即:负值是错误代码,正值是结果。Yu 访问哈希表时,总是很混乱,以指示是否找到了该项目并返回该项目。
ddyer

@SteveEvers Matlab sort函数通常对数组进行排序:sorted_array = sort(array)。有时我还需要相应的索引:[sorted_array, indices] = sort(array)。有时我希望索引:[~, indices]= sort(array). The function sort`实际上可以告诉您需要多少个输出参数,因此,如果2个输出与1个输出相比需要额外的工作,它只能在需要时计算这些输出。
gerrit

2

在大多数支持功​​能的语言中,您可以在任何可以使用该类型变量的地方使用函数调用:

x = n + sqrt(y);

如果该函数返回多个值,则将无法使用。动态类型的语言(例如python)将允许您执行此操作,但是,在大多数情况下,除非它可以解决与方程式中间的元组相关的问题,否则它将引发运行时错误。


5
只是不要使用不合适的功能。这与不返回值或返回非数字值的函数所带来的“问题”没有什么不同。
ddyer

3
在我使用的确实提供多个返回值的语言中(例如,SciLab),第一个返回值具有特权,将在仅需要一个值的情况下使用。因此,那里没有真正的问题。
Photon

而且即使没有它们,例如使用Python的元组解包,您也可以选择所需的一个:foo()[0]
Izkata

确切地说,如果一个函数返回2个值,那么它的返回类型是2个值,而不是单个值。编程语言不应该引起您的注意。
Mark E. Haase 2013年

1

我只想基于Harvey的答案。我最初是在新闻技术网站(arstechnica)上找到此问题的,并找到了一个令人惊奇的解释,我认为它确实回答了该问题的核心,而其他所有答案(除了Harvey的答案)都没有:

函数的单次返回源于机器代码。在机器代码级别,函数可以在A(累加器)寄存器中返回一个值。任何其他返回值将在堆栈上。

支持两个返回值的语言会将其编译为返回一个返回值并将第二个返回栈的机器码。换句话说,无论如何,第二个返回值最终都将作为out参数。

这就像问为什么赋值一次是一个变量。您可能使用的语言允许a,b = 1,2。但这最终会在机器代码级别为a = 1,然后是b = 2。

使编程语言结构与在编译和运行代码时实际发生的情况有一些相似之处,这是有一定道理的。


如果像C这样的低级语言将多个返回值作为一等功能,则C调用约定将包括多个返回值寄存器,就像在x86-x中最多使用6个寄存器来传递整数/指针函数args一样。 64系统V ABI。(实际上,x86-64 SysV确实返回了打包到RDX:RAX寄存器对中的最多16个字节的结构。这是很好的,如果该结构刚刚被加载并将被存储,但是与在单独的reg中包含单独的成员相比,这需要额外的打包操作。如果它们的宽度小于64位。)
Peter Cordes

明显的约定是RAX,然后是arg-pass regs。(RDI,RSI,RDX,RCX,R8,R9)。或在Windows x64约定中,为RCX,RDX,R8,R9。但是由于C本机没有多个返回值,因此C ABI /调用约定仅为宽整数和某些结构指定多个返回寄存器。请参阅《ARM®体系结构的过程调用标准:2个独立但相关的返回值》,以获取使用宽整数使编译器制作有效的asm以便在ARM上接收2个返回值的示例。
彼得·科德斯

-1

它从数学开始。以“公式转换”命名的FORTRAN是第一个编译器。FORTRAN过去是并且面向物理学/数学/工程学。

几乎和以前一样,COBOL没有明确的返回值。它几乎没有子例程。从那时起,大部分时间都是惯性。

例如,Go具有多个返回值,并且结果比使用“ out”参数更整洁,模棱两可。经过一点使用,它是非常自然和高效的。我建议为所有新语言考虑多个返回值。也许也适用于古老的语言。


4
这不能回答所问的问题
咬2013年

对我来说@gnat可以回答。OTOH一个人已经需要一些背景知识才能理解它,并且那个人可能不会问这个问题……
Netch

@Netch几乎不需要太多的背景来说明“ FORTRAN ...是第一个编译器”之类的语句一团糟。甚至没有错。就像其余的“答案”一样
咬2013年

链接说,已经有较早的编译器尝试,但是“由IBM的John Backus领导的FORTRAN团队通常被认为是在1957年推出了第一个完整的编译器”。问的问题是为什么只有一个?就像我说的。主要是数学和惯性。术语“功能”的数学定义仅需要一个结果值。因此,这是一种熟悉的形式。
RickyS 2013年

-2

它可能与处理器机器指令中如何进行函数调用的遗留问题以及所有编程语言均源自机器代码的事实有关,例如C-> Assembly-> Machine。

处理器如何执行功能调用

首先用机器代码编写程序,然后进行汇编。处理器通过将所有当前寄存器的副本压入堆栈来支持函数调用。从函数返回将从堆栈中弹出已保存的寄存器集。通常,一个寄存器保持不变,以允许返回函数返回一个值。

现在,为什么要这样设计处理器……这可能是资源紧张的问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.