为啥有这么个linkname
著名哲学家黑格尔曾说过,存在即合理。虽然本义并不是“事物既然存在了那么他就是合理的”,um…所以我也不知道linkname为什么存在
啥是linkname
我们来看看官方对于linkname的定义:
//go:linkname localname importpath.name
The //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported “unsafe”.
来,让我们用有道词典直接翻译下:
/ /:链接名localname importpath.name
//go:linkname指令指示编译器使用“import .name”作为目标文件符号名,表示在源代码中声明为“localname”的变量或函数。因为这个指令会破坏类型系统和包模块性,所以它只在导入了“不安全”的文件中启用。
虽然翻译的一塌糊涂,但是总比我看不懂英文要好一些。大概意思是说,//go:linkname这个指令在编译时把当前文件的私有变量or方法(即localname)链接到目标地(即importpath.name),由于这破坏了类型系统和包的模块化,所以使用时须导入unsafe包。
linkname使用
不废话,直接看看咋用的。
文件目录:
- go1006
- linkbase
- linked_funcs_vars.go
- use_link
- use_link_funcs_vars.go
- main.go
- linkbase
代码:
main.go:
package main
import (
"fmt"
"go1006/use_link"
)
func main() {
fmt.Printf("%v\n", use_link.Lkt)
sig1 := make(chan string)
sig2 := make(chan string)
go use_link.Notify(sig1)
go use_link.Notify(sig2)
select {
case t := <-sig1:
fmt.Printf("sig1: %v\n", t)
case t := <-sig2:
fmt.Printf("sig2: %v\n", t)
}
}
use_link_funcs_vars.go:
package use_link
import "C"
import (
_ "go1006/linkbase"
)
func Notify(c chan string)
var Lkt int
linked_funcs_vars.go:
package linkbase
import (
"math/rand"
"time"
_ "unsafe"
)
//go:linkname notify go1006/use_link.Notify
func notify(c chan string) {
rand.Seed(time.Now().UnixNano())
randSecond := rand.Intn(5)
time.Sleep(time.Second * time.Duration(randSecond))
c <- "hi"
}
//go:linkname lkt go1006/use_link.Lkt
var lkt = 10
来,看看这一段垃圾代码都写了些啥。
从下向上看,首先linkbase里面定义了非导出的方法和变量,并且把他们link到了use_link包里面对应的方法和变量上,导出方法是对应的导出方法or变量上无空行//go:linkname localname importpath.name,这个不像cgo的强制要求,按照C风格先列签名list再一一实现也是可以的,比如可以这样写:
package linkbase
import (
"math/rand"
"time"
_ "unsafe"
)
//go:linkname notify go1006/use_link.Notify
//go:linkname lkt go1006/use_link.Lkt
func notify(c chan string) {
rand.Seed(time.Now().UnixNano())
randSecond := rand.Intn(5)
time.Sleep(time.Second * time.Duration(randSecond))
c <- "hi"
}
var lkt = 10
然后注意import里面添加了_ “unsafe”,这个是必须的,没有你会看到报错:
//go:linkname only allowed in Go files that import “unsafe”.
然后看链接目的地use_link_funcs_vars.go,这里面只定义了Notify方法和Lkt变量,方法没有实现变量没有赋值,随便写这里可能会有报错:
missing function body.
为啥报错呢,来看golang编译内容,的一部分:
这个complete大概说每个包都compile一下?那我们的link目的地的包肯定没办法compile了,因为单独compile这个包的话方法没有body,所以会报missing function body.的错误,那么看gcflags -complete里面的说明,no C or assembly,所以你可以加一个import “C”,或者在包里面添加一个xx.s文件,那么就可以避免missing function body的错误啦。
PS: 其实这里还有一个方法是在no body的一侧使用linkname链接到具体的实现函数上,但是个人理解这样做不好,即当你修改被链接函数时不清楚哪些地方链接到了这里,会造成未知的影响,正确的用法还是应该是在具体实现处做导出。
然后来看main,这里直接打印了use_link包的Lkt变量并且直接使用了其中没有body的方法Notify,运行结果:
可以看到Lkt变量有值10,并且Notify方法正常使用。
那么最后加个思考,多级link是否可以呢?试了下,不可以的哦。