本文转载自微信公众号「脑子进煎鱼了」,作者陈煎鱼。转载本文请联系脑子进煎鱼了公众号。

专注于为中小企业提供网站设计制作、成都网站制作服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业纳溪免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了上千多家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
大家好,我是煎鱼。
最近看到了一个很有意思的话题,我们平时常常会用 Go 的内置函数 len 去获取各种 map、slice 的长度,那他是怎么实现的呢?
正当我想去看看 len 的具体实现时,一展身手,却发现竟然是个空方法:
- func len(v Type) int
 
看注解也没有 link 到其他 runtime 函数,那么 len 函数是如何被调用的呢?
先前也做了一些笔记,在此分享给大家,共同进步。
今天就由煎鱼带大家一同解开这个谜底。既然是谜底,那就一开始就揭开。
其实 Go 语言中并没有 len 函数的具体实现代码,他其实是 Go 编译器的 "魔法" ,不是实际的函数调用。
接下来将展开这部分,我们可以更深入地了解 Go 编译器的内部工作原理。
在 Go 编译器编译时会解析命令行参数中指定的标志和 Go 源文件,对解析后的 Go 包进行类型检查,将函数编译为机器代码。代码,最后将编译后的包定义写到磁盘上。
内部定义基本类型、内置函数和操作函数的阶段是在 types/universe.go 当中。同时会进行内置函数和具体的操作符匹配,可以明确知道内置函数 len 对应的是 OLEN:
- var builtinFuncs = [...]struct {
 - name string
 - op Op
 - }{
 - {"append", OAPPEND},
 - {"cap", OCAP},
 - {"close", OCLOSE},
 - {"complex", OCOMPLEX},
 - {"copy", OCOPY},
 - {"delete", ODELETE},
 - {"imag", OIMAG},
 - {"len", OLEN},
 - ...
 - }
 
在编译时,上分为五个阶段进行类型检查:
如果最后一个类型检查阶段遇到 len 函数,就会转换为 UnaryExpr 类型,一个 UnaryExpr 节点代表一个单数表达式,也最终就是不会成为函数调用:
- func typecheck1(n ir.Node, top int) ir.Node {
 - if n, ok := n.(*ir.Name); ok {
 - typecheckdef(n)
 - }
 - switch n.Op() {
 - ...
 - case ir.OCAP, ir.OLEN:
 - n := n.(*ir.UnaryExpr)
 - return tcLenCap(n)
 - }
 - }
 
在调用 *ir.UnaryExpr 转换完毕后,会调用 tcLenCap,也就是 typecheck,使用 okforlen 数组来验证参数的合法性或发出相关错误信息:
- func tcLenCap(n *ir.UnaryExpr) ir.Node {
 - n.X = Expr(n.X)
 - n.X = DefaultLit(n.X, nil)
 - n.X = implicitstar(n.X)
 - ...
 - var ok bool
 - if n.Op() == ir.OLEN {
 - ok = okforlen[t.Kind()]
 - } else {
 - ok = okforcap[t.Kind()]
 - }
 - ...
 - n.SetType(types.Types[types.TINT])
 - return n
 - }
 
经历过上面的步骤后在对所有内容进行类型检查后,所有函数都将排队等待编译:
- base.Timer.Start("be", "compilefuncs")
 - fcount := int64(0)
 - for i := 0; i < len(typecheck.Target.Decls); i++ {
 - if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
 - enqueueFunc(fn)
 - fcount++
 - }
 - }
 - base.Timer.AddEvent(fcount, "funcs")
 - compileFunctions()
 
在经过在 buildssa 和 genssa 之后,再深入几层,就会将 AST 树中的 len 表达式转换为 SSA。接着我们就可以看到 Go 语言中的每种类型的长度是怎么获取的。
这块的处理对应 internal/ssagen/ssa.go 的 expr 方法,如下:
- case ir.OLEN, ir.OCAP:
 - n := n.(*ir.UnaryExpr)
 - switch {
 - case n.X.Type().IsSlice():
 - op := ssa.OpSliceLen
 - if n.Op() == ir.OCAP {
 - op = ssa.OpSliceCap
 - }
 - return s.newValue1(op, types.Types[types.TINT], s.expr(n.X))
 - case n.X.Type().IsString(): // string; not reachable for OCAP
 - return s.newValue1(ssa.OpStringLen, types.Types[types.TINT], s.expr(n.X))
 - case n.X.Type().IsMap(), n.X.Type().IsChan():
 - return s.referenceTypeBuiltin(n, s.expr(n.X))
 - default: // array
 - return s.constInt(types.Types[types.TINT], n.X.Type().NumElem())
 - }
 
若是数组(array)类型,则会调用 NumElem 方法来获取长度值:
- type Array struct {
 - Elem *Type
 - Bound int64
 - }
 - func (t *Type) NumElem() int64 {
 - t.wantEtype(TARRAY)
 - return t.Extra.(*Array).Bound
 - }
 
若是字典(map)类型或通道(channel),将会调用 referenceTypeBuiltin 方法:
- func (s *state) referenceTypeBuiltin(n *ir.UnaryExpr, x *ssa.Value) *ssa.Value {
 - lenType := n.Type()
 - nilValue := s.constNil(types.Types[types.TUINTPTR])
 - cmp := s.newValue2(ssa.OpEqPtr, types.Types[types.TBOOL], x, nilValue)
 - b := s.endBlock()
 - b.Kind = ssa.BlockIf
 - b.SetControl(cmp)
 - b.Likely = ssa.BranchUnlikely
 - bThen := s.f.NewBlock(ssa.BlockPlain)
 - bElse := s.f.NewBlock(ssa.BlockPlain)
 - bAfter := s.f.NewBlock(ssa.BlockPlain)
 - ...
 - switch n.Op() {
 - case ir.OLEN:
 - s.vars[n] = s.load(lenType, x)
 - ...
 - return s.variable(n, lenType)
 - }
 
该函数的作用是是获取 map 或chan 的内存地址,并以零偏移量引用其结构布局,就像 unsafe.Pointer(uintptr(unsafe.Pointer(s)) 一样,返回第一个字面字段的值。
那为什么要获取结构体的第一个字段的值呢,应该是和 map 和 chan 的基础数据结构有关:
- type hmap struct {
 - count int
 - ...
 - }
 - type hchan struct {
 - qcount uint
 - ...
 - }
 
是因为 map 和 chan 的基础数据结构的第一个字段就表示长度,自然也就通过计算偏移值来获取了。
其他的数据类型,大家可以继续深入代码,再细看就好了。主要还是枚举多同类的数据类型,接着调用相应的方法。
每次我们看到内置函数时,总会下意识的以为是在 runtime 内实现的。看不到 runtime 内的实现方法,又会以为是通过注解 link 的方式来解决的。
但需要注意,其实还有像 len 内置函数这种直接编译器转换的,这也是一种不错的优化方式。
                本文标题:迷惑了,Golen()是怎么计算出来的?
                
                文章起源:http://www.csdahua.cn/qtweb/news46/114646.html
            
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网