标签 Package 下的文章

一文告诉你神奇的Go内建函数源码在哪里

img{512x368}

Go内建函数源码,我好像在哪里见过你。 – 佚名

1. 何为Go内建函数

众所周知,Go是最简单的主流编程语言之一,截至Go 1.15版本,Go语言的关键字的规模依旧保持在25个:

img{512x368}

很多刚入门的gopher可能会问:像bool、byte、error、true、iota甚至int都难道都不是关键字?没错!和其他语言不同,这些标识符并不是关键字,在Go中它们被称为预定义标识符。这些标识符拥有universe block作用域(关于go代码块作用域的详细解析,可参考我的技术专栏:“改善Go语⾔编程质量的50个有效实践”),可以在任何源码位置使用。

img{512x368}

从上图我们看到:所谓的Go内建函数也包含在这个预定义标识符集合中,只是这些标识符被用作函数名称标识符罢了。

2. 预定义标识符可被override

Go语言的关键字是保留的,我们无法将其用于规范之外的其他场合,比如作为变量的标识符。但是预定义标识符不是关键字,我们可以override它们。下面就是一个对默认表示整型类型的预定义标识符int进行override的例子:

package main

import (
        "fmt"
        "unsafe"
)

type int int8

func main() {
        var a int = 5
        fmt.Printf("%T\n", a) // main.int,而不是int
        fmt.Println(unsafe.Sizeof(a)) // 1,而不是8
}

在上述这个源文件中,预定义标识符int被override为一个自定义类型int,该类型的underlying type为int8,于是当我们输出该类型变量(代码中的变量a)的类型和长度时,我们得到的是main.int和1,而不是int和8。

3. 预定义标识符的声明源码在哪里

Go是开源的编程语言,这些预定义标识符想必也都有自己的“归宿”吧,的确是这样的。Go的每个发行版都带有一份源码,而预定义标识符就在这份源码中。

Go 1.14为例,我们可以在下面路径中找到预定义标识符的源码:

$GOROOT/src/builtin/builtin.go

以string、int、uint这几个代表原生类型的预定义标识符为例,它们的声明代码如下:

// $GOROOT/src/builtin/builtin.go

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

// int is a signed integer type that is at least 32 bits in size. It is a
// distinct type, however, and not an alias for, say, int32.
type int int

// uint is an unsigned integer type that is at least 32 bits in size. It is a
// distinct type, however, and not an alias for, say, uint32.
type uint uint

同时,我们利用go doc builtin.int也可以查看预定义标识符int的文档:

$go doc builtin.int
package builtin // import "builtin"

type int int
    int is a signed integer type that is at least 32 bits in size. It is a
    distinct type, however, and not an alias for, say, int32.

func cap(v Type) int
func copy(dst, src []Type) int
func len(v Type) int

4. 内建函数的源码在哪里?

作为预声明标识符子集的内建函数们在builtin.go中也都有自己的位置,比如:以append这个内建函数为例,我们可以在Go安装包的builtin.go中找到它的原型(Go 1.14):

// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
// slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type

但我们惊奇的发现:这里没有append函数的实现。那么append内建函数实现的源码究竟在哪里呢?本质上讲append函数,包括其他内建函数其实并没有自己的实现源码。

内建函数仅仅是一个标识符,在Go源码编译期间,Go编译器遇到内建函数标识符时会将其替换为若干runtime的调用,我们还以append函数为例,我们输出下面代码的汇编代码(Go 1.14):

// append.go
package main

import "fmt"

func main() {
 var s = []int{5, 6}
 s = append(s, 7, 8)  fmt.Println(s)
}

$go tool compile -S append.go > append.s

汇编节选如下(append.s):

"".main STEXT size=277 args=0x0 locals=0x58
 0x0000 00000 (xxx.go:5) TEXT "".main(SB), ABIInternal, $88-0
 0x0000 00000 (xxx.go:5) MOVQ (TLS), CX
 0x0009 00009 (xxx.go:5) CMPQ SP, 16(CX)
 0x000d 00013 (xxx.go:5) PCDATA $0, $-2
 0x000d 00013 (xxx.go:5) JLS  267
 0x0013 00019 (xxx.go:5) PCDATA $0, $-1
 0x0013 00019 (xxx.go:5) SUBQ $88, SP
 0x0017 00023 (xxx.go:5) MOVQ BP, 80(SP)
 0x001c 00028 (xxx.go:5) LEAQ 80(SP), BP
 0x0021 00033 (xxx.go:5) PCDATA $0, $-2
 0x0021 00033 (xxx.go:5) PCDATA $1, $-2
 0x0021 00033 (xxx.go:5) FUNCDATA $0, gclocals·69c1753bd5f81501d95132d08af04464(SB)
 0x0021 00033 (xxx.go:5) FUNCDATA $1, gclocals·568470801006e5c0dc3947ea998fe279(SB)
 0x0021 00033 (xxx.go:5) FUNCDATA $2, gclocals·bfec7e55b3f043d1941c093912808913(SB)
 0x0021 00033 (xxx.go:5) FUNCDATA $3, "".main.stkobj(SB)
 0x0021 00033 (xxx.go:6) PCDATA $0, $1
 0x0021 00033 (xxx.go:6) PCDATA $1, $0
 0x0021 00033 (xxx.go:6) LEAQ type.[2]int(SB), AX
 0x0028 00040 (xxx.go:6) PCDATA $0, $0
 0x0028 00040 (xxx.go:6) MOVQ AX, (SP)
 0x002c 00044 (xxx.go:6) CALL runtime.newobject(SB)
 0x0031 00049 (xxx.go:6) PCDATA $0, $1
 0x0031 00049 (xxx.go:6) MOVQ 8(SP), AX
 0x0036 00054 (xxx.go:6) MOVQ $5, (AX)
 0x003d 00061 (xxx.go:6) MOVQ $6, 8(AX)
 0x0045 00069 (xxx.go:7) PCDATA $0, $2
 0x0045 00069 (xxx.go:7) LEAQ type.int(SB), CX
 0x004c 00076 (xxx.go:7) PCDATA $0, $1
 0x004c 00076 (xxx.go:7) MOVQ CX, (SP)
 0x0050 00080 (xxx.go:7) PCDATA $0, $0
 0x0050 00080 (xxx.go:7) MOVQ AX, 8(SP)
 0x0055 00085 (xxx.go:7) MOVQ $2, 16(SP)
 0x005e 00094 (xxx.go:7) MOVQ $2, 24(SP)
 0x0067 00103 (xxx.go:7) MOVQ $4, 32(SP)
 0x0070 00112 (xxx.go:7) CALL runtime.growslice(SB)
 0x0075 00117 (xxx.go:7) PCDATA $0, $1
 0x0075 00117 (xxx.go:7) MOVQ 40(SP), AX
 0x007a 00122 (xxx.go:7) MOVQ 48(SP), CX
 0x007f 00127 (xxx.go:7) MOVQ 56(SP), DX
 0x0084 00132 (xxx.go:7) MOVQ $7, 16(AX)
 0x008c 00140 (xxx.go:7) MOVQ $8, 24(AX)
 0x0094 00148 (xxx.go:8) PCDATA $0, $0
 0x0094 00148 (xxx.go:8) MOVQ AX, (SP)
 0x0098 00152 (xxx.go:7) LEAQ 2(CX), AX
 0x009c 00156 (xxx.go:8) MOVQ AX, 8(SP)
 0x00a1 00161 (xxx.go:8) MOVQ DX, 16(SP)
 0x00a6 00166 (xxx.go:8) CALL runtime.convTslice(SB)
 ... ...

我们可以看到:append并没有以独立的身份出现在CALL汇编指令的后面,而是被换成:runtime.growslice、runtime.convTslice以及相关汇编指令了。


“Gopher部落”知识星球开球了!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!星球首开,福利自然是少不了的!2020年年底之前,8.8折(很吉利吧^_^)加入星球,下方图片扫起来吧!

我的Go技术专栏:“改善Go语⾔编程质量的50个有效实践”上线了,欢迎大家订阅学习!

img{512x368}

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网热卖中,欢迎小伙伴们订阅学习!

img{512x368}

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。

2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 微信公众号:iamtonybai
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • “Gopher部落”知识星球:https://public.zsxq.com/groups/51284458844544

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

没有VPS搭建govanityurls服务?别急!你依然可以自定义Go包导入路径

img{512x368}

我们见到的Go包的导入路径常常以github.com、bitbucket.org等代码托管站点的域名为前缀,这样的包导入路径有一个问题,那就是当Go包的托管站点发生变更时(比如从github.om迁移到bitbucket.org或gitlab.com),该包的使用者需要更新包的导入路径。当然,在支持go module+GOPROXY的情况下,如果使用者不再升级包版本,他/她完全可以继续使用原包导入路径,但这仅是特例。

还有一些包的导入路径并非以知名代码托管站点域名作为前缀,比如:Go官方扩展包text,它的包导入路径是golang.org/x/text,这种包导入路径被称为vanity import path,字面义是虚荣心导入路径,即以个人或组织官方域名作为前缀的包导入路径。采用vanity import path的包避免了包迁移对包使用者的影响。包使用者完全无需关心包的实际存储位置是在github上还是在bitbucket上或是私有服务器上。同时将vanity import path作为包的权威路径(canonical import path),也方便go get等对包权威路径的检查,避免包路径变更的前后不一致。

之前笔者曾经写过两篇文章介绍了利用govanityurls这个工具实现自定义包导入路径的方法。不过这种方法有一个约束条件,那就是你需要有一台VPS主机来部署运行govanityurls。虽然现在的云主机很便宜,但是购买和自建毕竟还是要付出一定成本的。如果没有VPS搭建govanityurls服务,那我们是否还有其他方法来自定义Go包导入路径呢?答案当然是

根据Go官方关于go get命令的文档,当go get从非知名托管站点(github, bitbucket等之外的站点)获取go包时,会尝试在返回的http/https应答head标签中查找是否有如下形式meta标签:

<meta name="go-import" content="import-prefix vcs repo-root">

meta标签中的name值是固定的”go-import”,import-prefix即包vanity import path,比如:go.tonybai.com/gocmpp;vcs是采用的版本控制工具,git、svn或hg等;repo-root是包代码的实际存储服务器url。

下面是一个实际例子:

<meta name="go-import" content="go.tonybai.com/gocmpp git https://github.com/bigwhite/gocmpp">

对于这样的标签,go get会做进一步匹配(可参见GOROOT/src/cmd/go/internal/get/vcs.go中的matchGoImport函数实现),看content值中的import-prefix是否是go get所需要的包的导入路径。如果是,则会向真正存储包代码的服务器再次发起代码获取请求(比如:git clone等)。

你可能会说:我用一个静态站点服务也能返回这样的应答。没错!但搭建静态站点一般还是需要VPS,这里我们介绍一种无须VPS的方法:利用github pages

下面是利用github pages实现自定义Go包导入路径的原理图:

img{512x368}

图:利用github pages实现自定义Go包导入路径

下面我们就以go.tonybai.com/gocmpp这个包导入路径的定制步骤来说明一下上述原理。

首先,我们要给tonybai.com这个域名添加一个子域名:go.tonybai.com作为我个人生产的所有Go包的导入路径前缀。我在DNS设置中为go.tonybai.com指定一个CNAME值:go.tonybai.com.github.io。这样当访问go.tonybai.com时,实际上是向go.tonybai.com.github.io发起请求。当然此刻如果你向go.tonybai.com发起请求时,你必然会得到404错误,因为github尚未建立起go.tonybai.com.github.io这个站点。

接下来,我们就来建立go.tonybai.com.github.io这个基于github pages的静态站点。我创建一个新的代码仓库:github.com/bigwhite/go.tonybai.com.github.io,在该仓库的”Settings”标签中,我们启用github pages,并将该仓库的master分支作为站点的根路径。在同一页面的Custom domain下,我们填入go.tonybai.com,点击save保存。github会在该仓库中创建一个名为CNAME的文件,其内容如下:

$cat CNAME

go.tonybai.com

表示该站点绑定了自定义域名:go.tonybai.com

正常情况下,你还可以在Settings标签下启用该静态站点的HTTPS服务,github会自动向Let’s Encrypt发起证书申请。

注:由于我的域名之前已经在Let’s Encrypt申请过相关证书,这里始终失败。这样导致后续我们只能使用go get -insecure去获取Go包代码。

在该仓库中,我们创建一个名为gocmpp的文件:

<html>
    <head>
        <meta name="go-import" content="go.tonybai.com/gocmpp git https://github.com/bigwhite/gocmpp">
        <meta http-equiv="refresh" content="0;URL='https://github.com/bigwhite/gocmpp'">
    </head>
    <body>
        Redirecting you to the <a href="https://github.com/bigwhite/gocmpp">project page</a>...
    </body>
</html>

该文件内容作为访问go.tonybai.com/gocmpp的请求的应答。

大约20分钟后,github pages内容生效。我们就可以使用下面命令去获取本存储在github.com/bigwhite/gocmpp下面的包了:

$go get go.tonybai.com/gocmpp

由于证书问题,这里我们只能用go get -insecure,即让go get使用http协议发起请求。

在gopath mode下,我们的执行结果如下:



$GO111MODULE=off go get -x -v -insecure go.tonybai.com/gocmpp
# get https://go.tonybai.com/gocmpp?go-get=1
# get https://go.tonybai.com/gocmpp?go-get=1: 200 OK (1.012s)
get "go.tonybai.com/gocmpp": found meta tag get.metaImport{Prefix:"go.tonybai.com/gocmpp", VCS:"git", RepoRoot:"https://github.com/bigwhite/gocmpp"} at //go.tonybai.com/gocmpp?go-get=1
go.tonybai.com/gocmpp (download)
cd .
git clone -- https://github.com/bigwhite/gocmpp /Users/tonybai/Go/src/go.tonybai.com/gocmpp
cd /Users/tonybai/Go/src/go.tonybai.com/gocmpp
git submodule update --init --recursive
cd /Users/tonybai/Go/src/go.tonybai.com/gocmpp
git show-ref
cd /Users/tonybai/Go/src/go.tonybai.com/gocmpp
git submodule update --init --recursive

.... ....

cd /Users/tonybai/Go/src/go.tonybai.com/gocmpp
/Users/tonybai/.bin/go1.14/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p go.tonybai.com/gocmpp -complete -buildid O9VmohLTciBDjallbacN/O9VmohLTciBDjallbacN -goversion go1.14 -D "" -importcfg $WORK/b001/importcfg -pack -c=4 ./activetest.go ./client.go ./conn.go ./connect.go ./deliver.go ./fwd.go ./packet.go ./receipt.go ./server.go ./submit.go ./terminate.go
/Users/tonybai/.bin/go1.14/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/ec/ec99b1c49c84d1e2edf88bee646f17198acc38c2c8f5a3d859540a394d6c5d0c-d # internal
mkdir -p /Users/tonybai/Go/pkg/darwin_amd64/go.tonybai.com/
mv $WORK/b001/_pkg_.a /Users/tonybai/Go/pkg/darwin_amd64/go.tonybai.com/gocmpp.a
rm -r $WORK/b001/
/Users/tonybai/go/src git:(master)  $tree -L 1 go.tonybai.com

go.tonybai.com
└── gocmpp

1 directory, 0 files

我们看到go get成功通过go.tonybai.com/gocmpp获取到gocmpp包,并编译安装成功(安装到GOPATH/pkg/下面)。

下面是module-aware模式下的go get获取结果:

$GOPROXY='direct' go get -insecure -x -v go.tonybai.com/gocmpp

# get https://go.tonybai.com/?go-get=1
# get https://go.tonybai.com/gocmpp?go-get=1
# get https://go.tonybai.com/?go-get=1: 200 OK (1.032s)
# get https://go.tonybai.com/gocmpp?go-get=1: 200 OK (1.056s)
get "go.tonybai.com/gocmpp": found meta tag get.metaImport{Prefix:"go.tonybai.com/gocmpp", VCS:"git", RepoRoot:"https://github.com/bigwhite/gocmpp"} at //go.tonybai.com/gocmpp?go-get=1
mkdir -p /Users/tonybai/Go/pkg/mod/cache/vcs # git3 https://github.com/bigwhite/gocmpp
... ...
0.017s # cd /Users/tonybai/Go/pkg/mod/cache/vcs/63c8ecfc5ed2c830894c13fd15ab1494ce9897aefba1d11c78740b046033e9ae; git cat-file blob 0f5a658fda5e029943f9b256fefe4fa4550e7906:go.mod
go get: go.tonybai.com/gocmpp@v0.0.0-20200715060927-0f5a658fda5e: parsing go.mod:
    module declares its path as: github.com/bigwhite/gocmpp
            but was required as: go.tonybai.com/gocmpp

我们看到go get同样获取到了gocmpp module,但是由于module-aware模式下,go get会对module根路径进行检查,因此go get发现了go.mod中的module根路径:github.com/bigwhite/gocmpp与要获取的module路径(go.tonybai.com/gocmpp)不符并报错。我们更新一下gocmpp项目中的go.mod内容后,这个问题将不复存在。

这样,我们在没有VPS的前提下也实现了自定义包导入路径。后续每当我创建一个新module或新包,我只需向该仓库(go.tonybai.com.github.io)提交一个以module或package名字命名的文件即可,就像上的gocmpp文件那样。

* 参考资料:https://gianarb.it/blog/go-mod-vanity-url

我的Go技术专栏:“改善Go语⾔编程质量的50个有效实践”上线了,欢迎大家订阅学习!

img{512x368}

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网上线>了,感谢小伙伴们学习支持!

img{512x368}

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接>口丰富,支持长短信,签名可选。

2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 微信公众号:iamtonybai
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily(Go每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



Statcounter View My Stats