我们在看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函数实现,这里相当于只是一个别名。该函数的定义如下图所示,但是分为nofakefake两种版本:

在这里插入图片描述

在这里插入图片描述

这种用法比较容易理解,定义好一个函数后,可以在其它包中进行多次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打开,其目录结构如下:

 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}

如果对你有帮助,欢迎点赞收藏!