Golang中的内置函数
go中存在着不少内置函数,此类函数并不需要引入相关package就可以直接使用该类函数。在go的源码builtin包的builtin.go中定义go所有的内置函数;但该文件仅仅是定义描述出了所有内置函数,并不包含函数的任何实现代码,该文件除了定义了内置函数还定义了部分内置类型;
内置函数使用
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)内存分配返回初始化的值,不同类型使用有所区别。
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中的指定key
内置函数len的实现
我们在builtin中仅仅只是看到了内置函数的定义描述,并没有函数的具体实现,也没有再其他包中找到具体的实现。那该内置函数到底是怎么实现的呢。
golang是一种编译型语言,go程序在运行前需要先通过编译器生成二进制码才能在目标机器上运行。go的内置函数处理正是藏身于编译器当中,下面将简单分析len内置函数的具体实现;
通常的编译器都包含了词法分析、语法分析、类型检查、中间代码生成、机器码生成这几个阶段,go编译器也不例外;
不同的计算机架构有着不同的机器码,直接把高级语言生成机器码相对比较困难,对高级语言的优化分析也不容易做,所以需要借助中间代码,go编译器所生成的中间代码具有静态单赋值特征(static single assigment, ssa),具有该特征的中间代码每个变量只会被赋值一次,通过该特征在中间代码分析时就可以明确发现哪些无效代码,机器码生成时就可减少某些无效指令,进而减少指令的执行。内置函数正是在中间代码阶段进行具体实现的;
len内置函数在编译器实现
在编译器的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的定义;
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函数所返回的值;
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函数。
参考资料:
文章首发地址:solinx
https://mp.weixin.qq.com/s/io5qjccql-mpjiatutdihq