如果你看過 Go 語言標准庫,應該有見到過,有一些函數只有簽名,沒有函數體。你有沒有感覺到很奇怪?這到底是怎么回事?我們自己可以這么做嗎?本文就來解密它。
首先,函數肯定得有實現,沒有函數體,一定是在其他某個地方。Go 中一般有兩種形式。
函數簽名使用Go,然后通過該包中的匯編文件來實現它
比如,在標准庫 sync/atomic
包中的函數基本只有函數簽名。比如:atomic.StoreInt32
// StoreInt32 atomically stores val into *addr.
func StoreInt32(addr *int32, val int32)
它的函數實現在哪呢?其實只要稍微留意一下發現該目錄下有一個文件:asm.s,它提供了具體的實現,即通過匯編來實現:
TEXT ·StoreInt32(SB),NOSPLIT,$0
JMP runtime∕internal∕atomic·Store(SB)
具體的實現,在 runtime∕internal
文件夾中,有興趣你可以打開 asm_amd64.s 看看。
很明顯,這種方式一方面會是效率的考慮,另一方面,有一些代碼只能匯編實現。
以上方式,你自己也可以嘗試。比如實現一個 Sum(a, b int) int
。歡迎評論給出你的代碼。
通過//go:linkname指令來實現
比如,在標准庫 time
包中的 Sleep
函數:
// Sleep pauses the current goroutine for at least the duration d.
// A negative or zero duration causes Sleep to return immediately.
func Sleep(d Duration)
它的實現在哪里呢?在 time 包中並沒有找到相應的匯編文件。
按照 Go 源碼的風格,這時候一般需要去 runtime
包中找。我們會找到 time.go,其中有一個函數:
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
...
}
這就是我們要找的 time.Sleep
的實現。
如果你有認真跟着學習「每日一學」,對於 //go:linkname
應該不陌生,這里的關鍵就在於這個指令,它的格式是:
//go:linkname 函數名 包名.函數名
因此我們在遇到函數沒有實現,但匯編又不存在時,可以通過嘗試搜索:go:linkname xxx xx.xxx
的形式來找,比如 time.Sleep
就可以通過 //go:linkname timeSleep time.Sleep
來查找具體實現在哪。
這里面要提示一點,使用 //go:linkname
,必須導入 unsafe
包,所以,有時候會見到:import _ "unsafe"
這樣的代碼。
一般來說,我們自己的代碼不會使用這樣的方式,但你會寫一個示例試試嗎?歡迎評論給出你的代碼。
另外,想想為什么 time.Sleep
的實現要這么搞?