3.2 多值返回

Go语言是支持多值返回的。怎么实现的呢?让我们先看一看C语言是如果返回多个值的。在C中如果想返回多个值,通常会在调用函数中分配返回值的空间,并将返回值的指针传给被调函数。

  1. int ret1, ret2;
  2. f(a, b, &ret1, &ret2)

被调函数被定义为下面形式,在函数中会修改ret1和ret2。对指针参数所指向的内容的修改会被返回到调用函数,用这种方式实现多值返回。

  1. void f(int arg1, int arg2, int *ret1, int *ret2);

所以,从表面上看Go的多值返回只不过像是这种实现方式的一个语法糖衣。其实简单的这么理解也没什么影响,但实际上Go不是这么干的,Go和我们常用的C编译器的函数调用协议是不同的。

假设我们定义一个Go函数如下:

  1. func f(arg1, arg2 int) (ret1, ret2 int)

Go的做法是在传入的参数之上留了两个空位,被调者直接将返回值放在这两空位,函数f调用前其内存布局是这样的:

  1. ret2保留空位
  2. ret1保留空位
  3. 参数3
  4. 参数2
  5. 参数1 <-SP

调用之后变为:

  1. ret2保留空位
  2. ret1保留空位
  3. 参数2
  4. 参数1 <-FP
  5. 保存PC <-SP
  6. f的栈
  7. ...

多值返回 - 图1

Go的C编译器按是plan9的C编译器实现的,在被调函数中对参数值的修改是会返回到调用函数中的。在函数体中设置ret1和ret2的值,实际上会被编译成这样:

  1. MOVQ BX,ret1+16(FP)
  2. ...
  3. MOVQ BX,ret2+24(FP)

对ret1+16(FP)的赋值其实是修改的调用函数的栈中的内容,这样就会将结果返回给调用函数了。这就是Go和C函数调用协议中很重要的一个区别:为了实现多值返回,Go是使用栈空间来返回值的。而常见的C语言是通过寄存器来返回值的。