go语言魔法技能go:linkname
我们在看Go语言的源码时,经常会看到一些特别的注释,比如:
1//go:build
2//go:linkname
3//go:nosplit
4//go:noescape
5//go:uintptrescapes
6//go:noinline
7//go:nowritebarrierrec
等等,这些特别的注释其实是Go编译器的指示指令。这里介绍一下go:linkname
指令其及用法,并给出各种用法的完整实例,网上很少有各种用法的完整实例的。
go:linkname
的指令格式为:
1//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
函数的上方:
这种用法比较容易出错,很容易出现missing function body
错误,这是因为Go在编译时会添加-complete
将该包作为一个纯Go包来编译,即该包中不包括非Go组件。
遇到这种情况,有两种方式解决:
- 在该包中添加一个空的
.s
文件,随便取一个名字,比如empty.s
- 在函数前添加
//go:linkname localname
格式的指令,注意没有importpath.name
三、实例
新建一个目录demo,使用VSCode打开,其目录结构如下:
1$ tree -a
2.
3├── .vscode
4│ ├── launch.json
5│ └── tasks.json
6├── case3
7│ ├── case3.go
8│ ├── empty.s
9│ └── internal
10│ └── priv.go
11├── go.mod
12├── main.go
13├── outer
14│ ├── internal
15│ │ └── inter.go
16│ └── outer.go
17├── private
18│ └── private.go
19└── public
20 └── public.go
21
228 directories, 11 files
main.go
1package main
2
3import (
4 "demo/outer"
5 "demo/public"
6)
7
8func main() {
9 public.Demo()
10 outer.Outer()
11}
go.mod
1module demo
2
3go 1.22.0
public.go
1package public
2
3import (
4 _ "demo/private"
5 _ "unsafe"
6)
7
8//go:linkname foo demo/private.foo
9func foo()
10
11func Demo() {
12 foo()
13}
private.go
1package private
2
3import (
4 "fmt"
5)
6
7func foo() {
8 fmt.Println("Private foo")
9}
outer.go
1package outer
2
3import (
4 _ "demo/outer/internal"
5 _ "unsafe"
6)
7
8//go:linkname Outer
9func Outer()
inter.go
1package internal
2
3import (
4 "fmt"
5 _ "unsafe"
6)
7
8//go:linkname inter demo/outer.Outer
9func inter() {
10 fmt.Println("internal.inter")
11}
case3.go
1package case3
2
3import _ "demo/case3/internal"
4
5func Foo()
empty.s
是一个空文件,用于告诉编译器,本包不是一个纯Go组件包
priv.go
1package internal
2
3import (
4 "fmt"
5 _ "unsafe"
6)
7
8//go:linkname f demo/case3.Foo
9func f() {
10 fmt.Println("internal.f")
11}
tasks.json
1{
2 "version": "2.0.0",
3 "tasks": [
4 {
5 "type": "go",
6 "label": "go: build workspace",
7 "command": "build",
8 "args": [
9 "./..."
10 ],
11 "problemMatcher": [
12 "$go"
13 ],
14 "group": {
15 "kind": "build",
16 "isDefault": true
17 },
18 "detail": "cd d:\\go; go build ./..."
19 }
20 ]
21}
launch.json
1{
2 // 使用 IntelliSense 了解相关属性。
3 // 悬停以查看现有属性的描述。
4 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 "version": "0.2.0",
6 "configurations": [
7 {
8 "name": "Launch Package",
9 "type": "go",
10 "request": "launch",
11 "mode": "auto",
12 "program": "${workspaceFolder}",
13 "preLaunchTask": "go: build workspace"
14 }
15 ]
16}
如果对你有帮助,欢迎点赞收藏!
- 原文作者:Witton
- 原文链接:https://wittonbell.github.io/posts/2024/2024-02-29-go语言魔法技能golinkname/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。