当前位置: 首页 > 编程笔记 >

通过汇编看golang函数的多返回值问题

温浩大
2023-03-14
本文向大家介绍通过汇编看golang函数的多返回值问题,包括了通过汇编看golang函数的多返回值问题的使用技巧和注意事项,需要的朋友参考一下

golang这门语言,有个比较好的特性,就是支持函数的多返回值。想C,C++,Java等这些语言,是不支持函数多返回的。但是C,C++可以使用传递指针,实现函数多返回。但是,你有没有想过,golang是怎样实现函数多返回值的呢?

我们知道,C,C++是通过寄存器实现函数返回值的,也就是先把返回值写入到一个寄存器中,然后再从寄存器中,读到函数的返回值。golang也是这样实现的吗?

伟大的思想家孔子曾说过,在源码面前一切都如同裸奔。后来,鲁迅先生,总结了孔子的思想,说出了,在汇编面前,一切语法都是纸老虎。

下面我们通过golang的汇编指令,来看一下golang是怎样实现函数的多返回值的

在看汇编之前,我们先用go的 debug 函数看下函数的栈信息

代码很简单,不用解释了

package main
import (
 "fmt"
 "runtime/debug"
)

func main() {
 one(3)
}

func one(a int) (int, int) {
 fmt.Println(string(debug.Stack()))
 return a, a + 5
}

 

我标红的这一行,就是 one 函数的栈信息,第一个参数 0x3 很好理解,就是我们传入的参数 3

, 但是后面这两个是啥?还有,我明明只传了一个参数,为啥会传入三个参数?

到这里,我也就不卖关子了,直接说了,后面这两个参数,就是one函数返回值的地址,也就是说,one函数返回值地址不在one函数中,而是在调用one函数的mian函数中。golang的函数返回值,和C,C++的不同,golang的返回值是通过栈内地址实现的(返回值的地址是由函数调用者提供)。

package main

func main() {
 var b, c *int
 one(3, b, c)
}

func one(a int, b, c *int) {
}

也就是说,刚开始的那段代码,和这段在功能实现上,没有什么差别,只是golang编译器提供的一个语法糖。

下面通过汇编来看一下

这次我们不是对深入分析golang的汇编,只是从汇编层面,验证我们之前结论(golang函数多返回问题)

所以,不会死磕plan9汇编语法,说实话,plan9的很多知识我也不懂,大学没开过汇编的课程,这些东西都是因为兴趣自学的。

golang用的是plan9汇编,看plan9之前,先了解一下plan9的几个概念

go汇编中有4个伪寄存器

  • FP: Frame pointer,指向栈底位置,一般用来引用函数的输入参数,用来访问函数的参数
  • PC: Program counter: 程序计数器,用于分支和跳转
  • SB: Static base pointer: 一般用于声明函数或者全局变量
  • SP: Stack pointer:指向当前栈帧的局部变量的开始位置(栈顶位置),一般用来引用函数的局部变量

我们用这段代码进行汇编

package main

func main() {
 one(3)
}

func one(a int) (int, int) {
 return a, a + 5
}

使用 go tool compile -N -l -S main.go 得到汇编代码

"".main STEXT nosplit size=2 args=0x0 locals=0x0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:3)  TEXT "".main(SB), NOSPLIT|ABIInternal, $0-0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:3)  FUNCDATA  $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:3)  FUNCDATA  $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:3)  FUNCDATA  $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:4)  PCDATA $2, $0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:4)  PCDATA $0, $0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:4)  XCHGL AX, AX
  0x0001 00001 (<unknown line number>) RET
  0x0000 90 c3           ..
"".one STEXT nosplit size=20 args=0x18 locals=0x0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:7)  TEXT "".one(SB), NOSPLIT|ABIInternal, $0-24
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:7)  FUNCDATA  $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:7)  FUNCDATA  $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:7)  FUNCDATA  $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:8)  PCDATA $2, $0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:8)  PCDATA $0, $0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:8)  MOVQ "".a+8(SP), AX
  0x0005 00005 (C:\Users\bruce\Desktop\go\main.go:8)  MOVQ AX, "".~r1+16(SP)
  0x000a 00010 (C:\Users\bruce\Desktop\go\main.go:8)  ADDQ $5, AX
  0x000e 00014 (C:\Users\bruce\Desktop\go\main.go:8)  MOVQ AX, "".~r2+24(SP)
  0x0013 00019 (C:\Users\bruce\Desktop\go\main.go:8)  RET
  0x0000 48 8b 44 24 08 48 89 44 24 10 48 83 c0 05 48 89 H.D$.H.D$.H...H.
  0x0010 44 24 18 c3          D$..

我只截取了和main,one函数相关的部分

TEXT "".one(SB), NOSPLIT|ABIInternal, $0-24 这行最后, $0-24 的含义,0代表one函数的栈帧大小(局部变量+可能需要的额外调用函数的参数空间的总大小),因为one函数中没有额外开销,所有大小是0,24是传入参数和返回值的大小,单位是字节。传入的参数和返回值都是 int ,在64位机器上,大小是8个字节,64位。

简单画一下栈的示意图

看一下这句 0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:8) MOVQ "".a+8(SP), AX

SP寄存器指向的是栈顶的位置, AX 是一个通用寄存器

MOVQ指令 把 参数a 也就是(SP+8)的值搬到AX中

0x0005 00005 (C:\Users\bruce\Desktop\go\main.go:8) MOVQ AX, "".~r1+16(SP)
同样,把AX中的值搬到r1(返回值b)

0x000a 00010 (C:\Users\bruce\Desktop\go\main.go:8) ADDQ $5, AX
ADDQ 指令把AX值+5

0x000e 00014 (C:\Users\bruce\Desktop\go\main.go:8) MOVQ AX, "".~r2+24(SP)
最后把AX的值搬到r2(返回值c)

0x0013 00019 (C:\Users\bruce\Desktop\go\main.go:8) RET
最后RET指令,one函数结束

总结

通过对golang进行汇编,真实了之前的结论

golang函数的多返回值不是通过寄存器传递,使用过使用调用值提供的地址,赋值实现的

先写这些吧,我也是刚接触golang的汇编,文中如有不正确的地方,还请指出

到此这篇关于通过汇编看golang函数的多返回值的文章就介绍到这了,更多相关汇编golang函数多返回值内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!

 类似资料:
  • 问题内容: 谁能告诉我如何将值作为函数的返回值返回。 当您点击注册表格中的提交时,上述功能就会触发(很明显)。问题是,无论响应是什么,此表单都将提交,并且警告框有时不显示任何内容(空白),有时根本不显示。但是,如果我将结尾更改为手动[即用] 替换,则正确的值将显示在警报框中。 PS我比JavaScript的菜鸟还差,因此也欢迎提出一般性改进代码的建议。 问题答案: 问题是,直到……等待……状态改变

  • Go语言内置支持多返回值,这个在Go语言中用的很多,比如一个函数同时返回结果和错误信息。 package main import "fmt" // 这个函数的返回值为两个int func vals() (int, int) { return 3, 7 } func main() { // 获取函数的两个返回值 a, b := vals() fmt.Println(a

  • 问题内容: 在我正在写的打印函数中,我试图根据switch语句的结果返回一个值;但是,我得到的错误太多,无法返回。 如果这个问题的答案很简单,请原谅我,但是函数有多少个参数可以返回一件事就不应该吗?还是需要为每个参数返回一件事。 这是我的代码。我在返回行上收到错误(返回的参数过多)。如何修复它,使其返回在switch语句中设置的字符串? 问题答案: 您需要指定输入参数后返回的内容,这不是pytho

  • 问题 你希望构造一个可以返回多个值的函数 解决方案 为了能返回多个值,函数直接return一个元组就行了。例如: >>> def myfun(): ... return 1, 2, 3 ... >>> a, b, c = myfun() >>> a 1 >>> b 2 >>> c 3 讨论 尽管myfun()看上去返回了多个值,实际上是先创建了一个元组然后返回的。 这个语法看上去比较奇怪,实际上我

  • 在rust中,任何函数都有返回类型,当函数返回时,会返回一个该类型的值。我们先来看看main函数: fn main() { //statements } 之前有说过,函数的返回值类型是在参数列表后,加上箭头和类型来指定的。不过,一般我们看到的main函数的定义并没有这么做。这是因为main函数的返回值是(),在rust中,当一个函数返回()时,可以省略。main函数的完整形式如下:

  • Lua 具有一项与众不同的特性,允许函数返回多个值。Lua 的库函数中,有一些就是返回多个值。 示例代码:使用库函数 string.find,在源字符串中查找目标字符串,若查找成功,则返回目标字符串在源字符串中的起始位置和结束位置的下标。 local s, e = string.find("hello world", "llo") print(s, e) -->output 3 5 返回多个值