是否有理由将大多数(?)编程语言中的函数设计为支持任意数量的输入参数,但仅支持一个返回值?

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

对此有解释吗?

评论

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

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

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

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

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

#1 楼

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

但是通常,即使在支持它们的语言中,也不经常使用多个返回值因为它们草率:


返回多个值的函数很难清楚地命名。

很容易误认为返回值的顺序
(password, username) = GetUsernameAndPassword()  


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

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

它们相当方便的地方是使用多种语言(例如Python)一个函数的返回值可以用作另一个函数的多个输入参数。但是,这是比使用类更好的设计的用例。

评论


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

–user69037
13年7月2日在23:27

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

– BlueRaja-Danny Pflughoeft
13年7月2日在23:42



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

–user69037
13年7月2日在23:46

...我从未声称元组不能用于其他用途。但是争论“ Python不支持多个返回值,它支持元组”简直是毫无意义的学问。这仍然是正确的答案。

– BlueRaja-Danny Pflughoeft
13年7月3日在3:25



元组和类都不是“多个值”。

– Andres F.
13年7月3日在17:25

#2 楼

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

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

评论


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

– Telastyn
13年7月2日在21:26

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

– tdammers
13年7月2日在21:50

@tdammers在早期,他们的想法非常相似。 Fortran,pascal等之类的东西比计算体系结构受数学影响更大。

–user40980
13年7月2日在22:00

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

– Telastyn
13年7月2日在22:11

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

– tdammers
13年7月4日在8:17

#3 楼

在数学中,“定义明确”的函数是给定输入只有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就足够了。

评论


另外,我真的很想能够用C#编写:var(值,成功)= ParseInt(“ foo”);由于声明了(int,bool)ParseInt(string s){},因此将进行编译时类型检查。我知道可以使用泛型来完成此操作,但仍然可以使语言更加美观。

–绝望的鬼脸
13年7月3日在12:30

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

–Domenic
13年7月3日在18:39

@沃伦:是的。请参阅此处,请注意,这样的解决方案将不是连续的:en.wikipedia.org/wiki/Well-definition

–史蒂文·埃弗斯(Steven Evers)
13年7月8日在21:24

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

– Michael Siler
13年12月22日在3:39

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

– Andres F.
13 Dec 24'0:09



#4 楼

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

评论


啊!非常有趣的细节。 +1!

–史蒂文·埃弗斯(Steven Evers)
13年7月2日在22:02

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

– imel96
13年7月2日在22:34

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

– BlueRaja-Danny Pflughoeft
13年7月2日在22:40



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

–洛伦佐·德马特(LorenzoDematté)
13年7月5日在7:02

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

– BlueRaja-Danny Pflughoeft
13年11月21日,0:30



#5 楼

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

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

这里有一些语言示例不支持返回多个值。我真的看不到如何增加对多个返回值的支持,从而使它们显着地表现出来,以抵消新语言功能的成本。 “ lang-rb prettyprint-override”> def foo; return 1, 2, 3 end one, two, three = foo one # => 1 three # => 3

Python

 def foo(): return 1, 2, 3

one, two, three = foo()

one
# >>> 1

three
# >>> 3
 


Scala

 def foo = (1, 2, 3)

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


Haskell

>
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


评论


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

– Gerrit
13年7月3日在10:08

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

– Gerrit
13年7月3日在10:17

@MikeCellini我不这么认为。一个函数可以告诉该函数调用了多少个输出参数([a,b,c] = func(某物))并相应地执行操作。例如,如果计算第一个输出参数很便宜而计算第二个输出参数很昂贵,则这很有用。我不熟悉在运行时可以使用等效Matlabs nargout的任何其他语言。

– Gerrit
13年11月24日在20:41

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

–Miles Rout
2014年2月4日23:32

@MilesRout:排序函数可以告诉它不需要计算索引吗?太酷了,我不知道。

–Jörg W Mittag
2014年2月4日在23:35

#6 楼

单个返回值如此受欢迎的真正原因是在许多语言中使用的表达式。在任何可以使用x + 1这样的表达式的语言中,您已经在考虑单一返回值,因为您可以通过将表达式分解成小段并确定每段的值来评估表达式。您查看x并确定其值为3(例如),然后查看1,然后查看x + 1并将其放在一起确定整体的值为4。表达式的每个句法部分都有一个值,没有其他数量的值;这是每个人都期望的表达式的自然语义。即使一个函数返回一对值,它实际上仍在返回一个正在执行两个值的值,因为返回不以某种方式包装到单个集合中的两个值的函数的想法太奇怪了。 />
人们不想处理使函数返回多个值所需要的替代语义。例如,在像Forth这样基于堆栈的语言中,您可以具有任意数量的返回值,因为每个函数仅修改堆栈的顶部,弹出输入并随意推送输出。这就是为什么Forth没有普通语言所具有的那种表达的原因。
Perl是另一种有时可以像函数一样返回多个值的语言,即使通常只考虑返回列表。 Perl中的“内插”列表方式为我们提供了(1, foo(), 3)之类的列表,其中可能包含3个元素,这与大多数不了解Perl的人所期望的一样,但也很容易只有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。

#7 楼

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


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


Fortran,Lisp和COBOL,第一个使用单个返回值,因为它是基于数学建模的。第二个返回任意数量的参数,方式与接收它们的方式相同:作为一个列表(也可以说它只传递并返回了一个参数:列表的地址)。第三种返回零或一个值。

这些第一种语言对跟随其后的语言的设计产生了很大影响,尽管唯一返回多个值的语言Lisp从未受到广泛欢迎。 />
当C语言问世时,它受到以前的语言的影响,但它非常注重有效地利用硬件资源,使C语言的功能与实现它的机器代码之间保持紧密的联系。它的一些最古老的功能(例如“ auto”和“ register”变量)是这种设计理念的结果。

还必须指出,汇编语言在80年代以前很流行。终于开始逐步退出主流发展。编写编译器和创建语言的人都熟悉汇编语言,并且在大多数情况下,都坚持在其中工作得最好。

大多数与该规范不同的语言从未受到欢迎,并且,因此,从来没有在影响语言设计师的决策方面发挥过强大的作用(当然,他们是受他们所了解的启发)。

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

在技​​术说明中,它具有5个寄存器以及一些1位标志。唯一的“满”寄存器是程序计数器(PC)-该寄存器指向要执行的下一条指令。其他寄存器中有累加器(A),两个“索引”寄存器(X和Y)以及堆栈指针(SP)。 SP,然后递减SP。从子例程返回则相反。一个人可以在堆栈中推入和拉出其他值,但是相对于SP而言,它很难引用内存,因此,编写可重入子例程非常困难。我们认为这是理所当然的,它在我们认为可以随时调用子例程的情况下,在这种体系结构上并不常见。通常,会创建一个单独的“堆栈”,以便将参数和子例程返回地址保持分开。索引寄存器(IX),与SP一样宽,可以从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上使用的8080和许多基于CP / M的微型计算机可以通过将SP压入堆栈,然后将其弹出到其间接寄存器HL中来实现与6800类似的功能。

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

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

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

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

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

至于数学……好吧,在我上数学课之前,我正在用计算机编程。 CS和编程语言的整个部分都受到数学的影响,但同样,整个部分都没有。

因此,我们将数学影响早期语言设计的因素融合在一起,其中硬件限制决定了易于实现的方面,流行语言在哪些方面影响了硬件的发展方式(在此过程中,Lisp机器和Forth机器处理器是必杀技)。

评论


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

–Daniel C. Sobral
13年12月24日在21:17

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

– gna
2013年12月24日21:20



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

– BitTickler
16-09-3在8:02

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

– Peter Cordes
18-09-26在4:28

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

– Peter Cordes
18-09-26在4:30

#8 楼

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

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个整数的函数。 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参数(在大多数情况下为2或允许使用更多这些方法。)

评论


+1关于功能语言如何优雅而轻松地进行处理。

– Andres F.
13年7月3日在17:29

从技术上讲,您仍然返回单个值still:D(只是将该单个值分解为多个值很简单)。

–托马斯·埃丁
13年7月12日在21:39

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

–超级猫
2014年1月28日在22:16

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

– BitTickler
16-09-3在8:12

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

– Maciej Piechotka
16-09-3在10:11

#9 楼

我认为这是由于诸如(a + b[i]) * c之类的表达式引起的。

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

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

#10 楼

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

评论


您能否提供一个示例,其中多个退货提供更清晰和/或更有效的代码?

–史蒂文·埃弗斯(Steven Evers)
13年7月2日在22:05

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

– Felix Dombek
13年7月2日在22:08

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

–doppelgreener
13年7月2日在22:09

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

– ddyer
13年7月2日在23:11



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

– Gerrit
13年7月3日在10:13



#11 楼

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

x = n + sqrt(y);


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

评论


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

– ddyer
13年7月3日在4:27

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

–光子
13年7月3日在4:45

即使它们不是,例如Python的元组解包,您也可以选择想要的一个:foo()[0]

–伊兹卡塔
13年11月20日在16:54

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

– Mark E. Haase
2013年12月22日18:40



#12 楼

我只想基于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
18-09-26在8:58

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

– Peter Cordes
18-09-26在8:59



#13 楼

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

COBOL几乎和以前一样,没有明确的返回值。它几乎没有子例程。从那时起,它一直都是惯性。例如,Go具有多个返回值,并且结果比使用“ out”参数更整洁,模棱两可。经过一点使用,它是非常自然和高效的。我建议为所有新语言考虑多个返回值。也许也适用于旧语言。

评论


这不能回答所问的问题

– gna
13年12月22日在12:06

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

– Netch
2013年12月22日13:43

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

– gna
2013年12月22日13:48



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

– RickyS
2013年12月24日6:26



#14 楼

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

处理器如何执行函数调用

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

现在,为什么要以这种方式设计处理器...这可能是资源限制的问题。