go语言魔法技能go:linkname

我们在看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函数实现,这里相当于只是一个别名。该函数的定义如下图所示,但是分为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打开,其目录结构如下:

$ 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"
        }
    ]
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值