解析go语言调用约定多返回值实现原理

目录
  • go简单代码反汇编
  • go语言调用约定分析
    • 1.C/C++调用约定类别
    • 2.go语言调用约定
  • go语言如何实现多返回值的
  • 总结

go简单代码反汇编

用简单的代码用以分析go的调用约定及多返回值的返回方式。

package main

func vals(c, d int) (a int, b int) {
	e := 1
	f := 2
	a = c + d + e + f
	b = d * 2
	return
}

func testMutil() {
	i, j := vals(1, 2)
	i = i + 1
	j = j + 1
}

func main() {
	testMutil()
}

编译go build -gcflags "-N -l" test.go并通过反编译软件获得部分汇编:

main_vals:
    sub     rsp, 18h
    mov     [rsp+18h+var_8], rbp
    lea     rbp, [rsp+18h+var_8]
    mov     [rsp+18h+arg_10], 0
    mov     [rsp+18h+arg_18], 0
    mov     [rsp+18h+var_10], 1
    mov     [rsp+18h+var_18], 2
    mov     rax, [rsp+18h+arg_0]
    add     rax, [rsp+18h+arg_8]
    add     rax, [rsp+18h+var_10]
    add     rax, 2
    mov     [rsp+18h+arg_10], rax
    mov     rax, [rsp+18h+arg_8]
    shl     rax, 1
    mov     [rsp+18h+arg_18], rax
    mov     rbp, [rsp+18h+var_8]
    add     rsp, 18h
    retn
main_vals endp

main_testMutil:
    mov     rcx, gs:28h
    mov     rcx, [rcx+0]
    cmp     rsp, [rcx+10h]
    jbe     short morestack_noctxt
    sub     rsp, 48h
    mov     [rsp+48h+var_8], rbp
    lea     rbp, [rsp+48h+var_8]
    mov     [rsp+48h+var_48], 1
    mov     [rsp+48h+var_40], 2
    call    main_vals
    mov     rax, [rsp+48h+var_38]
    mov     [rsp+48h+var_10], rax
    mov     rax, [rsp+48h+var_30]
    mov     [rsp+48h+var_18], rax
    mov     rax, [rsp+48h+var_10]
    mov     [rsp+48h+var_20], rax
    mov     rax, [rsp+48h+var_18]
    mov     [rsp+48h+var_28], rax
    mov     rax, [rsp+48h+var_20]
    inc     rax
    mov     [rsp+48h+var_20], rax
    mov     rax, [rsp+48h+var_28]
    inc     rax
    mov     [rsp+48h+var_28], rax
    mov     rbp, [rsp+48h+var_8]
    add     rsp, 48h
    retn
morestack_noctxt:
    call    runtime_morestack_noctxt
main_testMutil endp

go语言调用约定分析

1.C/C++调用约定类别

__stdcall调用约定:函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈。

_cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。注意:对于可变参数的成员函数,始终使用__cdecl的转换方式。

__fastcall调用约定:它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。

thiscall仅仅应用于”C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

naked call采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。

2.go语言调用约定

sub     rsp, 18h
mov     [rsp+18h+var_8], rbp
...
mov     rbp, [rsp+18h+var_8]
add     rsp, 18h

这段代码分别对应栈帧的构造与销毁。
根据反汇编并且调试,可以发现go语言参数是自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈。所以GO语言符合__stdcall调用约定。

go语言如何实现多返回值的

go语言可以返回多个返回值, 但同为编译型语言的C、C++却不支持。

1.C/C++返回值返回方式。

C/C++是通过eax/rax(32/64bit)寄存器返回的返回值。

2.go语言多返回值返回方式

可以看到vals函数的汇编,通过调试,可知arg_10与arg_18就是返回值a和b, arg_0与arg_8分别是参数c和d。其中

mov     [rsp+18h+arg_10], rax
...
mov     [rsp+18h+arg_18], rax

分别将参数值返回到参数上。之后在main_testMutil中将参数返回值拷贝到对应局部变量中

mov     rax, [rsp+48h+var_38]
mov     [rsp+48h+var_10], rax
mov     rax, [rsp+48h+var_30]
mov     [rsp+48h+var_18], rax

这就是go语言多返回值的实现方法了。

总结

go语言采用的是__stdcall调用约定。go多返回值是通过栈传递的。将多个返回值先传回参数上,函数栈帧销毁后并不会销毁参数部分(这里用作返回值),再将参数部分进行拷贝然后再参与运算。

以上就是解析go语言调用约定多返回值实现原理的详细内容,更多关于go调用约定多返回值的资料请关注我们其它相关文章!

(0)

相关推荐

  • go 协程返回值处理操作

    我就废话不多说了,大家还是直接看代码吧~ package main import "fmt" import "sync" var ch = make(chan int) func do(lock *sync.Mutex, ct *int) { lock.Lock() *ct++ lock.Unlock() ch <- 1 } func main() { fmt.Println("hello thread") var ct = 0 lock

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

    golang这门语言,有个比较好的特性,就是支持函数的多返回值.想C,C++,Java等这些语言,是不支持函数多返回的.但是C,C++可以使用传递指针,实现函数多返回.但是,你有没有想过,golang是怎样实现函数多返回值的呢? 我们知道,C,C++是通过寄存器实现函数返回值的,也就是先把返回值写入到一个寄存器中,然后再从寄存器中,读到函数的返回值.golang也是这样实现的吗? 伟大的思想家孔子曾说过,在源码面前一切都如同裸奔.后来,鲁迅先生,总结了孔子的思想,说出了,在汇编面前,一切语法都是

  • Golang的func参数及返回值操作

    参数及返回值 参数一指定数据类型为int 参数二 (-interface{}) 可传任何多个不同类型的参数 返回值:单个返回值直接指定数据类型可以不使用 (),多个返回值需使用().各返回值之间使用逗号分隔 func main() { demo.Params(10, 20, "golang", true) } func Params(id int, params ...interface{}) (error, error) { fmt.Println(id) fmt.Println(p

  • go调用shell命令两种方式实现(有无返回值)

    目录 阻塞方式(需要执行结果) 适用于执行普通非阻塞shell命令,且需要shell标准输出的 非阻塞方式(不需要执行结果) 阻塞方式(需要执行结果) 主要用于执行shell命令,并且返回shell的标准输出 适用于执行普通非阻塞shell命令,且需要shell标准输出的 //阻塞式的执行外部shell命令的函数,等待执行完毕并返回标准输出 func exec_shell(s string) (string, error){ //函数返回一个*Cmd,用于使用给出的参数执行name指定的程序 c

  • 解析go语言调用约定多返回值实现原理

    目录 go简单代码反汇编 go语言调用约定分析 1.C/C++调用约定类别 2.go语言调用约定 go语言如何实现多返回值的 总结 go简单代码反汇编 用简单的代码用以分析go的调用约定及多返回值的返回方式. package main func vals(c, d int) (a int, b int) { e := 1 f := 2 a = c + d + e + f b = d * 2 return } func testMutil() { i, j := vals(1, 2) i = i

  • C语言函数调用约定和返回值详情

    目录 一.函数调用约定 1. 影响函数生成的符号名 2. 影响形参内存的释放者 _stdcall _fastcall _thiscall 二.函数的返回值 1. 0 < 返回值 <= 4字节 2. 4字节 < 返回值 <= 8字节 3. 返回值 > 8字节 一.函数调用约定 _cdecl:C调用约定 _stdcall:Windows标准的调用约定 _fastcall:快速调用约定 _thiscall:C++的成员函数调用约定 以上的函数调用约定入参都是从右向左,只有PASCA

  • 关于mybatis调用存储过程获取返回值问题

    总体思路:map传值 controller: Map<String,Object> m=new HashMap<String,Object>(); m.put("name", 'zs'); m.put("password", '55555'); cardservice.bindCard(m); JSONObject json=new JSONObject(); //获得返回值 json.put("msg", m.get(&

  • javascript中声明函数的方法及调用函数的返回值

    <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <!--js中声明函数的方法--> <script type="text/javascript"> //因为javascript是弱类型的语言,所以参数不需要加类型.函数的也不需要像c#那样要求所以路径都需要有返回值(这个不像c#语言,而且c#的方法也不需要在方法

  • PHP入门教程之自定义函数用法详解(创建,调用,变量,参数,返回值等)

    本文实例讲述了PHP自定义函数用法.分享给大家供大家参考,具体如下: Demo1.php <?php //标准函数,内置函数 echo md5('123456'); echo '<br/>'; echo sha1('123456'); echo '阅谁问君诵,水落清香浮.'; ?> Demo2.php <?php //创建函数,不要跟系统的内置函数重名 //函数有个特性,必须调用,才可以执行 //无参数表示()里面是空的,无返回就是函数的程序里没有 return functi

  • C# ODP.NET 调用Oracle函数返回值时报错的一个解决方案

    有人在社区问到:C#调用Oracle中自定义函数的返回值时,无法正常调用.但在PL/SQL中正常调用返回. 于是动手一试: 1.准备函数(Oralce 11g.2.0.0.4) CREATE OR REPLACE FUNCTION F_Update_Grade(v_UserID in Number) return nvarchar2 is V_Grade nVARCHAR2(20); begin V_Grade := '1205'; update TESTDB3 set Grade = V_Gr

  • C语言main函数的参数及其返回值详细解析

    返回值的作用 main函数的返回值用于说明程序的退出状态.如果返回0,则代表程序正常退出:返回其它数字的含义则由系统决定.通常,返回非零代表程序异常退出.下面我们在winxp环境下做一个小实验.首先编译下面的程序:int main( void ){    return 0;}然后打开附件里的"命令提示符",在命令行里运行刚才编译好的可执行文件,然后输入"echo%ERRORLEVEL%",回车,就可以看到程序的返回值为0.假设刚才编译好的文件是a.exe,如果输入&

  • C语言 用指针作为函数返回值详解

    C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数.下面的例子定义了一个函数 strlong(),用来返回两个字符串中较长的一个: #include <stdio.h> #include <string.h> char *strlong(char *str1, char *str2){ if(strlen(str1) >= strlen(str2)){ return str1; }else{ return str2; } } int main(){ cha

  • C#调用Python程序传参数获得返回值

    目录 说明 1. Python 脚本 2. 打包成Windows可执行文件 3. C# 程序 4. 参考 说明 C# 调用 Python 程序有多种方式,本篇用的是第 4 种: nuget的ironPython: 用 c/c++ 调用python,再封装成库文件,c# 调用: c# 命令行调用.py文件执行: python 程序制作成 .exe 可执行文件,c# 使用命令行进行传参取返回值. 1. Python 脚本 先建个测试脚本 d://Test/EchoHi.py 代码如下: import

  • shell函数内调用另一个函数(不带返回值和带返回值)

    目录 一.函数B调用不带返回值的函数A 二.函数B调用带返回值的函数A,并接收函数A的返回值进行输出 一.函数B调用不带返回值的函数A 新建文件,命名为 test.sh,添加如下代码: #!/bin/bash # 即将被调用的函数A function A(){ a="aaa" echo $a } # 函数B,直接调用A function B(){ A echo "bbb" } B 命令行中通过sh test.sh执行结果: 二.函数B调用带返回值的函数A,并接收函数

随机推荐