我们在看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.go
8 directories, 11 files
main.go
package main
import (
"demo/outer"
"demo/public"
)
func main() {
public.Demo()
outer.Outer()
}
go.mod
module demo
go 1.22.0
public.go
package public
import (
_ "demo/private"
_ "unsafe"
)
//go:linkname foo demo/private.foo
func foo()
func Demo() {
foo()
}
private.go
package private
import (
"fmt"
)
func foo() {
fmt.Println("Private foo")
}
outer.go
package outer
import (
_ "demo/outer/internal"
_ "unsafe"
)
//go:linkname Outer
func Outer()
inter.go
package internal
import (
"fmt"
_ "unsafe"
)
//go:linkname inter demo/outer.Outer
func inter() {
fmt.Println("internal.inter")
}
case3.go
package case3
import _ "demo/case3/internal"
func Foo()
empty.s
是一个空文件,用于告诉编译器,本包不是一个纯Go组件包
priv.go
package internal
import (
"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"
}
]
}
如果对你有帮助,欢迎点赞收藏!