图 2

文章插图
如果看到的图 2 真的发生的话,你将遇到一个问题 。指针指向了栈下的无效地址空间 。当 main 函数调用下一个函数,指向的内存将重新映射并将被重新初始化 。
这就是逃逸分析将开始保持完整性的地方 。在这种情况下,编译器将检查到,在 createUserV2 的(函数)栈中构造 user 值是不安全的,因此,替代地,会在堆中构造(相应的)值 。这(个分析并处理的过程)将在第 28 行构造时立即发生 。

文章插图
可读性(Readability)
在上一篇博文中,我们知道一个函数只能直接访问它的(函数栈)空间,或者通过(函数栈空间内的)指针,通过跳转访问(函数栈空间外的)外部内存 。这意味着访问逃逸到堆上的值也需要通过指针跳转 。
记住 createUserV2 的代码的样子:
清单 4

文章插图
语法隐藏了代码中真正发生的事情 。第 28 行声明的变量 u 代表一个 user 类型的值 。Go 代码中的类型构造不会告诉你值在内存中的位置 。所以直到第 34 行返回类型时,你才知道值需要逃逸(处理) 。这意味着,虽然 u 代表类型 user 的一个值,但对该值的访问必须通过指针进行 。
你可以在函数调用之后,看到堆栈就像(图 3)这样 。
图 3

文章插图
在 createUserV2 函数栈中,变量 u 代表的值存在于堆中,而不是栈 。这意味着用 u 访问值时,使用指针访问而不是直接访问 。你可能想,为什么不让 u 成为指针,毕竟访问它代表的值需要使用指针?
清单 5

文章插图
如果你这样做,将使你的代码缺乏重要的可读性 。(让我们)离开整个函数一秒,只关注 return 。
清单 6
34 return u35 }这个 return 告诉你什么了呢?它说明了返回 u 值的副本给调用栈 。然而,当你使用 & 操作符,return 又告诉你什么了呢?
清单 7
34 return &u35 }多亏了 & 操作符,return 告诉你 u 被分享给调用者,因此,已经逃逸到堆中 。记住,当你读代码的时候,指针是为了共享,& 操作符对应单词 "sharing" 。这在提高可读性的时候非常有用,这(也)是你不想失去的部分 。
清单 8

文章插图
为了让其可以工作,你一定要通过共享指针变量(的方式)给(函数) json.Unmarshal 。json.Unmarshal 调用时会创建 user 值并将其地址赋值给指针变量 。https://play.golang.org/p/koI8EjpeIx
代码解释:
01:创建一个类型为 user,值为空的指针 。
02:跟函数 json.Unmarshal 函数共享指针 。
03:返回 u 的副本给调用者 。
这里并不是很好理解,user值被 json.Unmarshal 函数创建,并被共享给调用者 。
如何在构造过程中使用语法语义来改变可读性?
清单 9

文章插图
代码解释:
01:创建一个类型为 user,值为空的变量 。
02:跟函数 json.Unmarshal 函数共享 u 。
03:跟调用者共享 u 。
这里非常好理解 。第 02 行共享 user 值到调用栈中的 json.Unmarshal,在第 03 行 user 值共享给调用者 。这个共享过程将会导致 user 值逃逸 。
在构建一个值时,使用值语义,并利用 & 操作符的可读性来明确值是如何被共享的 。
编译器报告(Compiler Reporting)
想查看编译器(关于逃逸分析)的决定,你可以让编译器提供一份报告 。你只需要在调用 go build 的时候,打开 -gcflags 开关,并带上 -m 选项 。
实际上总共可以使用 4 个 -m,(但)超过 2 个级别的信息就已经太多了 。我将使用 2 个 -m 的级别 。
清单 10

文章插图
你可以看到编译器报告是否需要逃逸处理的决定 。编译器都说了什么呢?请再看一下引用的 createUserV1 和 createUserV2 函数 。
清单 13

文章插图
从报告中的这一行开始 。
推荐阅读
- 百度与谷歌搜索机制十大区别
- 前端入门:重学Cookie与Session
- 清新福建·运动之旅“寿宁高山茶杯”山径赛激情开跑
- 任嘉伦搞笑视频 任嘉伦搞笑图片
- 世界三大高香红茶之的大吉岭红茶
- 印度大吉岭红茶之等级划分
- 多肉虹之玉和虹之玉锦 多肉虹之玉锦怎么养
- 人际交往的意义有哪些 人际交往的重要性
- 钱串 钱串怎么砍头繁殖
- 微信被“拉黑”之后浑然不知?别再蒙在鼓里,这些方法能有效辨别
