1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 12:32:21 +00:00

ch3-04-fix typo

This commit is contained in:
sfw 2018-06-27 18:02:09 +08:00
parent 2df98c8223
commit deef6f2305

View File

@ -31,7 +31,7 @@ TEXT ·Add(SB), $0
第一种是最完整的写法函数名部分包含了当前包的路径同时指明了函数的参数大小为24个字节对应参数和返回值的3个int类型。第二种写法则比较简洁省略了当前包的路径和参数的大小。需要注意的是标志参数中的NOSPLIT如果在Go语言函数声明中通过注释指明了标志应该也是可以省略的需要确认下
目前可能遇到的函数函数标志有NOSPLIT、WRAPPER和NEEDCTXT几个。其中NOSPLIT不会生成或包含栈分裂代码这一般用于没有任何其它函数调用的叶子函数这样可以适当提高性能。WRAPPER标志则表示这个是一个包装函数在panic或runtime.caller等某项处理函数帧的地方不会增加函数帧计数。最后的NEEDCTXT表示需要一个上下参数,一般用于闭包函数。
目前可能遇到的函数函数标志有NOSPLIT、WRAPPER和NEEDCTXT几个。其中NOSPLIT不会生成或包含栈分裂代码这一般用于没有任何其它函数调用的叶子函数这样可以适当提高性能。WRAPPER标志则表示这个是一个包装函数在panic或runtime.caller等某项处理函数帧的地方不会增加函数帧计数。最后的NEEDCTXT表示需要一个上下参数,一般用于闭包函数。
需要注意的是函数也没有类型上面定义的Add函数签名可以下面任意一种格式
@ -47,7 +47,7 @@ func Add() (a []int) // reflect.SliceHeader 切片头刚好也是 3 个 int 成
## 函数参数和返回值
对于函数来说,最重要是函数对外提供的API约定包含函数的名称、参数和返回值。当名称和参数返回都确定之后如何精确计算参数和返回值的大小是第一个需要解决的问题。
对于函数来说,最重要是函数对外提供的API约定包含函数的名称、参数和返回值。当名称和参数返回都确定之后如何精确计算参数和返回值的大小是第一个需要解决的问题。
![](../images/ch3-func-decl-02.ditaa.png)
@ -83,7 +83,7 @@ TEXT ·Foo(SB), $0
func SomeFunc(a, b int, c bool) (d float64, err error) int
```
函数的参数有不同的类型,同时含多个返回值,而且返回值中含有更复杂的接口类型。我们该如何计算每个参数的位置和总的大小呢?
函数的参数有不同的类型,同时含多个返回值,而且返回值中含有更复杂的接口类型。我们该如何计算每个参数的位置和总的大小呢?
其实函数参数和返回值的大小以及对齐问题和结构体的大小和成员对齐问题是一致的。我们先看看如果用Go语言函数来模拟Foo函数中参数和返回值的地址
@ -145,9 +145,9 @@ func SomeFunc(FP *SomeFunc_args_and_returns) {
![](../images/ch3-func-local-var-01.ditaa.png)
为了便于访问局部变量Go汇编语言引入了伪SP寄存器对应当前栈帧的底部。因为在当前栈帧时栈的底部是固定不变的因此局部变量的相对于伪SP的偏移量也就是固定的这可以简化局部变量的维护工作。SP真伪区分只有一个原则如果使用SP时有一个临时标识符前缀就是伪SP否则就是真SP寄存器。比如`a(SP)``b+8(SP)`有a和b临时前缀这里都是伪SP而前缀部分一般用于表示局部变量的名字。而`(SP)``+8(SP)`没有临时标识符作为前缀它们都是真SP寄存器。
为了便于访问局部变量Go汇编语言引入了伪SP寄存器对应当前栈帧的底部。因为在当前栈帧时栈的底部是固定不变的因此局部变量的相对于伪SP的偏移量也就是固定的这可以简化局部变量的维护工作。SP真伪区分只有一个原则如果使用SP时有一个临时标识符前缀就是伪SP否则就是真SP寄存器。比如`a(SP)``b+8(SP)`有a和b临时前缀这里都是伪SP而前缀部分一般用于表示局部变量的名字。而`(SP)``+8(SP)`没有临时标识符作为前缀它们都是真SP寄存器。
在X86平台函数的调用栈是从高地址向低地址增长的因此伪SP寄存器对应栈帧的底部其实是对应更大的地址。当前栈的顶部对应真实存在的SP寄存器对应对应当前函数栈帧的栈底对应更小的地址。如果整个内容是用Memory数组表示那么`Memory[0(SP):end-0(SP)]`就是对应当前栈帧的切片其中开始位置是真SP结尾部分是伪SP。真SP一般用于表示调用其它函数时的参数和返回值真SP对应内存较低的地址所以被访问变量的偏移量是正数而伪SP对应高地址对应的局部变量的偏移量都是负数。
在X86平台函数的调用栈是从高地址向低地址增长的因此伪SP寄存器对应栈帧的底部其实是对应更大的地址。当前栈的顶部对应真实存在的SP寄存器对应当前函数栈帧的栈底对应更小的地址。如果整个内容是用Memory数组表示那么`Memory[0(SP):end-0(SP)]`就是对应当前栈帧的切片其中开始位置是真SP结尾部分是伪SP。真SP一般用于表示调用其它函数时的参数和返回值真SP对应内存较低的地址所以被访问变量的偏移量是正数而伪SP对应高地址对应的局部变量的偏移量都是负数。
我们现在Go语言定义一个Foo函数并在函数内部定义几个局部变量
@ -165,7 +165,7 @@ TEXT ·Foo(SB), $24-0
RET
```
Foo函数有3个int类型的局部变量但是没有调用其它的函数所以函数的栈帧大小为24个字节。因为Foo函数没有参数和返回值因此参数和返回值大小为0个字节当然这个部分可以省略不写。而局部变量中先定义的变量a离为SP对应的地址最远最后定义的变量c里伪SP最近。有两个隐私导致出现这种逆序的结果一个从Go语言函数角度理解先定义的a变量地址要比后定义的变量的地址更小另一个是伪SP对应栈帧的底部而栈是从高向地生长的所以有着更小地址的a变量离栈的底部伪SP更远。
Foo函数有3个int类型的局部变量但是没有调用其它的函数所以函数的栈帧大小为24个字节。因为Foo函数没有参数和返回值因此参数和返回值大小为0个字节当然这个部分可以省略不写。而局部变量中先定义的变量a离为SP对应的地址最远最后定义的变量c里伪SP最近。有两个原因导致出现这种逆序的结果一个从Go语言函数角度理解先定义的a变量地址要比后定义的变量的地址更小另一个是伪SP对应栈帧的底部而栈是从高向地生长的所以有着更小地址的a变量离栈的底部伪SP更远。
我们同样可以通过结构体来模拟局部变量的布局:
@ -187,9 +187,9 @@ func Foo() {
## 调用其它函数
常见的用Go汇编实现的函数都是叶子函数也就是被其它函数调用但是很少调用其它函数。这主要是因为叶子函数比较简单可以简化汇编函数的编写同时一般性能或特性的瓶颈也处于叶子函数。但是能够调用其它函数和能够被其它函数调用通用重要否则Go汇编就不是一个完整的汇编语言。
常见的用Go汇编实现的函数都是叶子函数也就是被其它函数调用但是很少调用其它函数。这主要是因为叶子函数比较简单可以简化汇编函数的编写同时一般性能或特性的瓶颈也处于叶子函数。但是能够调用其它函数和能够被其它函数调用同样重要否则Go汇编就不是一个完整的汇编语言。
在前文中我们已经学习过一些汇编实现的函数参数和返回值处理的规则。那么一个显然的问题是,汇编函数的参数是从哪里来的?答案同样明显,被调用函数的参数是有调用方准备的:调用方在栈上设置好空间和数据后调用函数,被调用方在返回前将返回值放如对应的位置函数通过RET指令返回调用放函数之后调用方从返回值对应的栈内存位置取出结果。Go语言函数的调用参数和返回值均是通过栈传输的这样做的点是函数调用栈比较清晰缺点是函数调用有一定的性能损耗Go编译器是通过函数内联来缓解这个问题的影响
在前文中我们已经学习过一些汇编实现的函数参数和返回值处理的规则。那么一个显然的问题是,汇编函数的参数是从哪里来的?答案同样明显,被调用函数的参数是有调用方准备的:调用方在栈上设置好空间和数据后调用函数,被调用方在返回前将返回值放在对应的位置函数通过RET指令返回调用方函数之后调用方从返回值对应的栈内存位置取出结果。Go语言函数的调用参数和返回值均是通过栈传输的这样做的点是函数调用栈比较清晰缺点是函数调用有一定的性能损耗Go编译器是通过函数内联来缓解这个问题的影响
![](../images/ch3-func-call-frame-01.ditaa.png)