背景

  1. 在看源码时,一些源码办法没有办法体,难道阐明这些办法为空?例如:time.Now 调用的 now(), time.Sleep , reflect.makechan
// Provided by package runtime.func now() (sec int64, nsec int32, mono int64)func Sleep(d Duration)func makechan(typ *rtype, size int) (ch unsafe.Pointer)
  1. 在写代码时,如果咱们想应用别的包下没有导出的办法或者变量时,怎么操作

go:linkname 的用法

实际上,上述提到的三个没有办法体的办法,其实现都在 src/runtime包下

  1. time.now timestub.go 文件中
//go:linkname time_now time.nowfunc time_now() (sec int64, nsec int32, mono int64) {    sec, nsec = walltime()    return sec, nsec, nanotime()}
  1. time.Sleep time.go 文件中
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.//go:linkname timeSleep time.Sleepfunc timeSleep(ns int64) {   if ns <= 0 {      return   }   gp := getg()   t := gp.timer   if t == nil {      t = new(timer)      gp.timer = t   }   t.f = goroutineReady   t.arg = gp   t.nextwhen = nanotime() + ns   if t.nextwhen < 0 { // check for overflow.      t.nextwhen = maxWhen   }   gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)}
  1. reflect.makechan
//go:linkname reflect_makechan reflect.makechanfunc reflect_makechan(t *chantype, size int) *hchan {    return makechan(t, size)}

咱们能够看到具体实现都具备 go:linkname, 能够揣测就是这个货色把两个不同包下的函数链接到一起了

官网介绍

//go:linkname localname [importpath.name]

这个非凡的指令的作用域并不是紧跟的下一行代码,而是同一个包下失效。//go:linkname通知 Go 的编译器把本地的(公有)变量或者办法localname链接到指定的变量或者办法importpath.name。简略来说,localname import.name指向的变量或者办法是同一个。因为这个指令毁坏了类型零碎和包的模块化准则,只有在引入 unsafe 包的前提下能力应用这个指令。

应用实例

文件构造如下图所示, inner 包模仿 go 源码包/内部引入包,outer包须要调用inner包中的办法, main.go 调试

 tree.├── go.mod├── inner│   └── inner.go├── main├── main.go└── outer    ├── 1.s    └── outer.go

空body办法复现:

模仿办法无 body办法,实在实现在另外一个包中的案例

outer.go

package outerimport (    _ "github.com/he2121/demos/linkname_example/inner"    // 实在办法在 inner 包,必须援用这个编译器能力找到链接)func Hello()

如果呈现:missing function body 在包内加一个 .s文件。因为go build默认加会加上-complete参数,加这个 .s 可绕开这个限度。

inner.go

package innerimport (    _ "unsafe")//go:linkname hello github.com/he2121/demos/linkname_example/outer.Hellofunc hello()  {    println("hello")}

main.go

package mainimport "github.com/he2121/demos/linkname_example/outer"func main()  {    outer.Hello()}// output:// hello

这就是 go 源码中这么多没有 body 的办法的起因了,个别到 src/runtime包中搜寻就能发现 //go:linkname的实现。

理论应用

实现中,如果咱们有一些骚操作须要应用源代码/第三方包中的未导出变量/办法,就可应用 //go:linkname实现, 如下例子

outer.go: 应用 inner 包中的未导出变量/办法

package outerimport (    _ "unsafe"    _ "github.com/he2121/demos/linkname_example/inner")//go:linkname A github.com/he2121/demos/linkname_example/inner.avar A int//go:linkname Hello github.com/he2121/demos/linkname_example/inner.hellofunc Hello()

inner.go

package innervar a = 100func hello()  {    println("hello")}

main.go

package mainimport "github.com/he2121/demos/linkname_example/outer"func main()  {    outer.Hello()    println(outer.A)}// output// hello// 100

示例 repo

总结

  1. 能读懂多一点源码
  2. 把握获取并应用其它包中未导出变量/办法的骚操作

参考

  1. https://cloud.tencent.com/dev...
  2. https://blog.csdn.net/lastswe...
  3. https://studygolang.com/artic...