如果你看过 Go 语言标准库,应该有见到过,有一些函数只有签名,没有函数体。你有没有感觉到很奇怪?这到底是怎么回事?我们自己可以这么做吗?本文就来解密它。 首先,函数肯定得有实现,没有函数体,一定是在其他某个地方。Go 中一般有两种形式。 ### 函数签名使用Go,然后通过该包中的汇编文件来实现它 比如,在标准库`sync/atomic`包中的函数基本只有函数签名。比如:`atomic.StoreInt32` ~~~ // StoreInt32 atomically stores val into *addr.2func StoreInt32(addr *int32, val int32)3 ~~~ 它的函数实现在哪呢?其实只要稍微留意一下发现该目录下有一个文件:asm.s,它提供了具体的实现,即通过汇编来实现: ~~~ TEXT ·StoreInt32(SB),NOSPLIT,$02    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.2// A negative or zero duration causes Sleep to return immediately.3func Sleep(d Duration)4 ~~~ 它的实现在哪里呢?在 time 包中并没有找到相应的汇编文件。 按照 Go 源码的风格,这时候一般需要去`runtime`包中找。我们会找到 time.go,其中有一个函数: ~~~ // timeSleep puts the current goroutine to sleep for at least ns nanoseconds.2//go:linkname timeSleep time.Sleep3func timeSleep(ns int64) {4    ...5} ~~~ 这就是我们要找的`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`的实现要这么搞?