我们在看Go语言的源码时,经常会看到一些特别的注释,比如:
//go:build
//go:linkname
//go:nosplit
//go:noescape
//go:uintptrescapes
//go:noinline
//go:nowritebarrierrec
等等,这些特别的注释其实是Go编译器的指示指令。这里介绍一下go:linkname
指令其及用法,并给出各种用法的完整实例,网上很少有各种用法的完整实例的。
go:linkname
的指令格式为:
//go:linkname localname [importpath.name]
localname
为本包中的名字importpath.name
为引入包的路径及其名字,可省略。
在使用该指令前,需要import
unsafe
包。
该指令写在localname
上,但localname
可以是importpath.name
的别名,也可以是它的实现,即可以是在本包中定义,也可以不是定义。下面就以具体例子来说明:
一、localname
函数在本包未实现,相当于是别名
可以看Go源码中time包的runtimeNano
函数,如下图:
runtimeNano
函数在此并未实现,只是提供了一个声明,使用//go:linkname runtimeNano runtime.nanotime
来告诉编译器该函数使用runtime
包中的nanotime
函数实现,这里相当于只是一个别名。该函数的定义如下图所示,但是分为nofake
与fake
两种版本:
这种用法比较容易理解,定义好一个函数后,可以在其它包中进行多次go:linkname
创建别名。
二、localname
函数在本包实现
细心的读者应该发现了上图中time_now
函数了,即通过//go:linkname time_now time.now
指示time
包中的now
函数使用本包(runtime
包)中的time_now
函数,当然time包中还是需要有一个now
函数的声明,就在前面nanotime
函数的上方:
可以看到now
函数上面的注释没有任何编译器指令。
这种用法比较容易出错,很容易出现missing function body
错误,这是因为Go在编译时会添加-complete
将该包作为一个纯Go包来编译,即该包中不包括非Go组件。
遇到这种情况,有两种方式解决:
- 在该包中添加一个空的
.s
文件,随便取一个名字,比如empty.s
- 在函数前添加
//go:linkname localname
格式的指令,注意没有importpath.name
三、实例
新建一个目录demo,使用VSCode打开,其目录结构如下:
$ tree -a
.
├── .vscode
│ ├── launch.json
│ └── tasks.json
├── case3
│ ├── case3.go
│ ├── empty.s
│ └── internal
│ └── priv.go
├── go.mod
├── main.go
├── outer
│ ├── internal
│ │ └── inter.go
│ └── outer.go
├── private
│ └── private.go
└── public└── public.go8 directories, 11 files
main.go
package mainimport ("demo/outer""demo/public"
)func main() {public.Demo()outer.Outer()
}
go.mod
module demogo 1.22.0
public.go
package publicimport (_ "demo/private"_ "unsafe"
)//go:linkname foo demo/private.foo
func foo()func Demo() {foo()
}
private.go
package privateimport ("fmt"
)func foo() {fmt.Println("Private foo")
}
outer.go
package outerimport (_ "demo/outer/internal"_ "unsafe"
)//go:linkname Outer
func Outer()
inter.go
package internalimport ("fmt"_ "unsafe"
)//go:linkname inter demo/outer.Outer
func inter() {fmt.Println("internal.inter")
}
case3.go
package case3import _ "demo/case3/internal"func Foo()
empty.s
是一个空文件,用于告诉编译器,本包不是一个纯Go组件包
priv.go
package internalimport ("fmt"_ "unsafe"
)//go:linkname f demo/case3.Foo
func f() {fmt.Println("internal.f")
}
tasks.json
{"version": "2.0.0","tasks": [{"type": "go","label": "go: build workspace","command": "build","args": ["./..."],"problemMatcher": ["$go"],"group": {"kind": "build","isDefault": true},"detail": "cd d:\\go; go build ./..."}]
}
launch.json
{// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"name": "Launch Package","type": "go","request": "launch","mode": "auto","program": "${workspaceFolder}","preLaunchTask": "go: build workspace"}]
}
如果对你有帮助,欢迎点赞收藏!