理解函数的调用过程
2020-09-30 本文已影响0人
梦工厂
一、函数的范围
二、函数的调用
2.1 函数的进入
2.2 函数的退出
2.3. 函数返回值的传递 (不同编译器、不同平台是不一样的)
2.4. Go为什么可以返回多个值?
一、函数的范围
ebp寄存器指向函数的栈底,esp寄存器指向函数的栈顶,共同划分了函数的活动范围。
减小esp的值相当于在栈上开辟空间,而增大esp的值相当于在栈上回收空间;
二、函数的调用
2.1 函数的进入
- 参数入栈,从右到左,有的通过寄存器传递;
- call function(1将当前指令的下一个指令地址压入栈中.2跳转到函数体执行)
-
push ebp
: 把ebp压入栈中(old ebp); -
mov ebp,esp
: ebp=esp - [可选]
sub esp,XXX
: 在栈上分配XXX字节的临时空间; - [可选]
push XXX
:保存寄存器的值,可以保证寄存器在函数调用前后保持不变;
栈的变化
2.2 函数的退出
- [可选]
pop XXX
:如有必要,恢复保存过的寄存器; -
mov esp,ebp
: 恢复esp同时回收局部的变量空间; -
pop ebp
: 从栈中恢复保存的ebp的值; -
ret
: 从栈中取得返回地址,并跳转到该位置;
2.3. 函数返回值的传递 (不同编译器、不同平台是不一样的)
- 函数将返回值存储到eax寄存器中,返回后函数的调用方再去读取eax;
- eax本身只有4个字节,对于5~8个字节对象的情况,惯例是通过eax和edx联合返回. eax储存返回值低4字节,而edx存储返回值高1~4字节;
- 对于更大的对象:
- 调用方函数A额外开辟一片空间,称为temp;
- 将temp的地址作为隐藏函数传递给函数B;
- 函数B将数据拷贝给temp对象,并将temp对象的地址用eax传出;
- 函数B返回后,函数A将eax指向的temp对象的内容拷贝给相应变量;
2.4. Go为什么可以返回多个值?
不同平台对于函数有不同的调用规范.
- 例如32位通过栈传递参数, 通过eax寄存器传递返回值.
- 64位windows通过rcx, rdx, r8, r9传递前4个参数, 通过栈传递第5个开始的参数, 通过eax寄存器传递返回值.
- 64位linux, unix通过rdi, rsi, rdx, rcx, r8, r9传递前6个参数, 通过栈传递第7个开始的参数, 通过eax寄存器传递返回值.
- go并不使用这些调用规范(除非涉及到与原生代码交互), go有一套独自的调用规范. 所有参数都通过栈传递, 返回值也通过栈传递。go函数可以有多个返回值的原因也在于此. 因为返回值都通过栈传递了。
汇编分析Go语言函数的调用