Go中存在着很多内置函数,此类函数并不须要引入相关Package就能够直接使用该类函数。在Go的源码builtin包的builtin.go中定义Go全部的内置函数;但该文件仅仅是定义描述出了全部内置函数,并不包含函数的任何实现代码,该文件除了定义了内置函数还定义了部份内置类型;git
内置函数使用github
len(“123”) println(“log”) fmt.Println(“fmt”) // 非内置函数使用,调用fmt包中的函数
close: 用于发送方关闭chan,仅适用于双向或发送通道。
len、cap: 用于获取数组、Slice、map、string、chan类型数据的长度或数量,len返回长度、cap返回容量;
new、make: new用于值类型、用户定义类型的内存分配,new(T)将分配T类型零值返回其指向T类型的指针;make用于引用类型(Slice、map、chan)内存分配返回初始化的值,不一样类型使用有所区别。golang
make(chan int) 建立无缓冲区的通道 make(chan int,10) 建立缓冲区为10的通道 make([]int,1,5) 建立长度为1,容量为5的slice make([]int,5) 建立容量长度都为5的slice make(map[int] int) 建立不指定容量的map make(map[int] int,2) 建立容量为2的map
copy、apped: 用于复制slice与为slice追加元素;
print、println: 用于打印输出;
panic、recover: 用于错误处理;
delete: 用于删除map中的指定keyexpress
咱们在builtin中仅仅只是看到了内置函数的定义描述,并无函数的具体实现,也没有再其余包中找到具体的实现。那该内置函数究竟是怎么实现的呢。
Golang是一种编译型语言,Go程序在运行前须要先经过编译器生成二进制码才能在目标机器上运行。Go的内置函数处理正是藏身于编译器当中,下面将简单分析len内置函数的具体实现;
一般的编译器都包含了词法分析、语法分析、类型检查、中间代码生成、机器码生成这几个阶段,Go编译器也不例外;
不一样的计算机架构有着不一样的机器码,直接把高级语言生成机器码相对比较困难,对高级语言的优化分析也不容易作,因此须要借助中间代码,Go编译器所生成的中间代码具备静态单赋值特征(Static Single Assigment, SSA),具备该特征的中间代码每一个变量只会被赋值一次,经过该特征在中间代码分析时就能够明确发现哪些无效代码,机器码生成时就可减小某些无效指令,进而减小指令的执行。内置函数正是在中间代码阶段进行具体实现的;数组
在编译器的cmd\compile\internal\gc\universe.go类中能够看到每一个内置函数在编译器中对应着一个Op(Operator);架构
var builtinFuncs = [...]struct { name string op Op }{ {"append", OAPPEND}, {"cap", OCAP}, {"close", OCLOSE}, {"complex", OCOMPLEX}, {"copy", OCOPY}, {"delete", ODELETE}, {"imag", OIMAG}, {"len", OLEN}, {"make", OMAKE}, {"new", ONEW}, {"panic", OPANIC}, {"print", OPRINT}, {"println", OPRINTN}, {"real", OREAL}, {"recover", ORECOVER}, }
len函数对应的Op为OLEN;
在cmd\compile\internal\gc\syntax.go 语法树相关的定义中咱们亦可看到OLEN的定义;app
len函数支持获取多种类型变量的长度或容量,这也就说明了该函数的实现可能不仅有一种或许每种类型对应着一种实现;
len函数支持的类型有:string、array、slice、map,chan;从中咱们能够简单把类型分为两类:string、数组为长度固定的,slice、map、chan为动态长度的。针对固定长度类型len是看成常量来实现的;函数
len对于固定长度类型的实现:
在编译器的源码cmd\compile\internal\gc\const.go中咱们能够发现evconst函数有这样一段代码;优化
func evconst(n *Node) { ... // Pick off just the opcodes that can be constant evaluated. switch op := n.Op; op { case OCAP, OLEN: fmt.Println("const:",nl,nl.Type.Etype) switch nl.Type.Etype { case TSTRING: if Isconst(nl, CTSTR) { setintconst(n, int64(len(strlit(nl)))) } case TARRAY: if !hascallchan(nl) { setintconst(n, nl.Type.NumElem()) } } ... } ... }
这段代码中能够看到针对string类型是直接获取nl的长度放入到Node当中的,该节点为AST的literal节点。此处的nl为len所接收的字符串;
针对数组类型也相似直接获取数组的长度写入常量;
此处所写入的值也就是len函数所返回的值;ui
len对动态长度类型的的实现:
在编译器源码cmd\compile\internal\gc\ssa.go中有这么一段代码:
// expr converts the expression n to ssa, adds it to s and returns the ssa result. func (s *state) expr(n *Node) *ssa.Value { ... case OLEN, OCAP: switch { case n.Left.Type.IsSlice(): op := ssa.OpSliceLen if n.Op == OCAP { op = ssa.OpSliceCap } v:= s.newValue1(op, types.Types[TINT], s.expr(n.Left)) fmt.Println("ssa...",v.LongString(),"-",n.Left.Op) return v case n.Left.Type.IsString(): // string; not reachable for OCAP v:=s.newValue1(ssa.OpStringLen, types.Types[TINT], s.expr(n.Left)) fmt.Println("string...",v.LongString(),n.Left.Op) return v case n.Left.Type.IsMap(), n.Left.Type.IsChan(): return s.referenceTypeBuiltin(n, s.expr(n.Left)) default: // array fmt.Println("array:",n.Left.Type.NumElem()) return s.constInt(types.Types[TINT], n.Left.Type.NumElem()) } ... }
从中能够看到针对各类类型的处理,此处也有string与array类型的处理但并未执行到,未发现起到了做用,如知道请告知;
针对slice类型此处转成了OpSliceLen操做,在 builtin优化阶段将 经过 (SliceLen (SliceMake _ len _)) -> len直接替换为slice的长度,此处调用的代码为:cmd\compile\internal\ssa\rewritegeneric.go中的rewriteValuegeneric_OpSliceLen函数;
针对map/chan类型,此处调用了referenceTypeBuiltin函数。
参考资料:
https://github.com/golang/go
文章首发地址:Solinx
https://mp.weixin.qq.com/s/iO5qjcCql-MPJiatUtdiHQ