關於Go語言中的go:linkname

在閱讀time包源碼的時候,遇到了一個特別奇怪的函數定義,類似於C語言中的函數聲明一樣,只有函數定義,沒有函數體,代碼如下:

func startTimer(*runtimeTimer)

func stopTimer(*runtimeTimer) bool

我當時非常驚訝,以爲自己學的是假的go語法,當問了萬能的谷歌之後,我得到的答案是如下,參見 go中的定時器timer

AfterFunc很簡單,就是把參數封裝爲runtimeTimer,然後啓動timer(把timer添加到隊列中), 這部分代碼在runtime/time.go中,注意這裏goFunc新啓動了一個goroutine來執行用戶的任務,這樣用戶的func就不會堵塞timer

重點在這一句:這部分代碼在runtime/time.go中

於是就去runtime/time,go 中尋找這兩個函數的詳細實現:

// startTimer adds t to the timer heap.
//go:linkname startTimer time.startTimer
func startTimer(t *timer) {
if raceenabled {
racerelease(unsafe.Pointer(t))
}
addtimer(t)
}

// stopTimer removes t from the timer heap if it is there.
// It returns true if t was removed, false if t wasn't even there.
//go:linkname stopTimer time.stopTimer
func stopTimer(t *timer) bool {
return deltimer(t)
}

於是新的疑惑又涌上心頭,go:linkname 又是什麼東西呢?於是我去查了go的官方文檔,得到的是這樣的答案

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”.

大概的意思就是說 //go:linkname的目的是告訴編譯器使用importpath.name來對本來不可導出的(localname)函數或者變量實現導出功能。由於這種方法是破壞了go語言的模塊化規則的,所以必須在導入了"unsafe"包的情況下使用。

我們現在基本清楚了,爲什麼需要go:linkname的原因:

由於go語法規則限制,小寫字母開頭的函數或者變量是本模塊私有的,不可被包外的代碼訪問;但是如果必須要能被外部模塊訪問到,又要限制爲私有方法呢?只能在編譯器上做手腳,通過一個特殊的標記來實現這種功能

清楚了go:linkname的使用場景,那我們如何使用它呢?下面是個簡單的例子:

文件樹如下:

Timer
├── controller
│   ├── b.go
│   └── xxx.s
├── main.go
└── view
    └── a.go

文件a.go內容:

package view

import (
	"fmt"
	_ "unsafe"
)

//go:linkname printHelloWorld Timer/controller.printHelloWorld
func printHelloWorld() {
	fmt.Println("hello world!")
}

func PrintHelloWorld() {
	fmt.Println("hello world!")
}

文件b.go內容:

package controller

import _ "Timer/view"

func printHelloWorld()

func PrintHelloWorld() {
	printHelloWorld()
}

文件 main.go 內容:

package main

import (
	"Timer/controller"
)

func main() {
	controller.PrintHelloWorld()
	//view.PrintHelloWorld()
}

但是當我運行代碼的時候發現瞭如下問題:

# Timer/controller
controller/b.go:5:6: missing function body

在經過谷歌之後,才知道是如下原因:

Go在編譯的時候會啓用-complete編譯器flag,它要求所有的函數必需包含函數體。創建一個空的彙編語言文件繞過這個限制

於是我在controller目錄下創建一個xxx.s的空文件,當存在 .s 文件時,編譯器則不會再加 -complete 參數;完整的文件樹如下:

Timer
├── controller
│   ├── b.go
│   └── xxx.s
├── main.go
└── view
└── a.go

於是我們運行,終於成功了:

➜ Timer go run main.go
hello world!

以上介紹了go:linkname 的用法,但是這種方法會破壞代碼的模塊性,所以不建議在業務代碼中使用這種方式。

發佈了129 篇原創文章 · 獲贊 203 · 訪問量 45萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章