一文告訴你神奇的 Go 內建函式原始碼在哪裡
Go 內建函式原始碼,我好像在哪裡見過你。 - 佚名
1. 何為 Go 內建函式
眾所周知,Go 是最簡單的主流程式語言之一,截至Go 1.15 版本,Go 語言的關鍵字的規模依舊保持在 25 個:
很多剛入門的 gopher 可能會問:像 bool、byte、error、true、iota甚至 int 都難道都不是關鍵字?沒錯!和其他語言不同,這些識別符號並不是關鍵字,在 Go 中它們被稱為預定義識別符號。這些識別符號擁有universe block 作用域(關於 go 程式碼塊作用域的詳細解析,可參考我的技術專欄:“改善 Go 語⾔程式設計質量的 50 個有效實踐”),可以在任何原始碼位置使用。
從上圖我們看到:所謂的Go 內建函式也包含在這個預定義識別符號集合中,只是這些識別符號被用作函式名稱識別符號罷了。
2. 預定義識別符號可被 override
Go 語言的關鍵字是保留的,我們無法將其用於規範之外的其他場合,比如作為變數的識別符號。但是預定義識別符號不是關鍵字,我們可以 override 它們。下面就是一個對預設表示整型型別的預定義識別符號int進行 override 的例子:
package main
import (
"fmt"
"unsafe"
)
type int int8
func main() {
var a int = 5
fmt.Printf("%T\n", a) // main.int,而不是int
fmt.Println(unsafe.Sizeof(a)) // 1,而不是8
}
在上述這個原始檔中,預定義識別符號 int 被 override 為一個自定義型別 int,該型別的 underlying type 為 int8,於是當我們輸出該型別變數 (程式碼中的變數 a) 的型別和長度時,我們得到的是 main.int 和 1,而不是 int 和 8。
3. 預定義識別符號的宣告原始碼在哪裡
Go 是開源的程式語言,這些預定義識別符號想必也都有自己的 “歸宿” 吧,的確是這樣的。Go 的每個發行版都帶有一份原始碼,而預定義識別符號就在這份原始碼中。
以Go 1.14為例,我們可以在下面路徑中找到預定義識別符號的原始碼:
$GOROOT/src/builtin/builtin.go
以 string、int、uint 這幾個代表原生型別的預定義識別符號為例,它們的宣告程式碼如下:
// $GOROOT/src/builtin/builtin.go
// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string
// int is a signed integer type that is at least 32 bits in size. It is a
// distinct type, however, and not an alias for, say, int32.
type int int
// uint is an unsigned integer type that is at least 32 bits in size. It is a
// distinct type, however, and not an alias for, say, uint32.
type uint uint
同時,我們利用go doc builtin.int
也可以檢視預定義識別符號 int 的文件:
$go doc builtin.int
package builtin // import "builtin"
type int int
int is a signed integer type that is at least 32 bits in size. It is a
distinct type, however, and not an alias for, say, int32.
func cap(v Type) int
func copy(dst, src []Type) int
func len(v Type) int
4. 內建函式的原始碼在哪裡?
作為預宣告識別符號子集的內建函式們在 builtin.go 中也都有自己的位置,比如:以 append 這個內建函式為例,我們可以在 Go 安裝包的 builtin.go 中找到它的原型 (Go 1.14):
// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
// slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type
但我們驚奇的發現:這裡沒有 append 函式的實現。那麼 append 內建函式實現的原始碼究竟在哪裡呢?本質上講 append 函式,包括其他內建函式其實並沒有自己的實現原始碼。
內建函式僅僅是一個識別符號,在 Go 原始碼編譯期間,Go 編譯器遇到內建函式識別符號時會將其替換為若干 runtime 的呼叫,我們還以 append 函式為例,我們輸出下面程式碼的彙編程式碼 (Go 1.14):
// append.go
package main
import "fmt"
func main() {
var s = []int{5, 6}
s = append(s, 7, 8)
fmt.Println(s)
}
$go tool compile -S append.go > append.s
彙編節選如下 (append.s):
"".main STEXT size=277 args=0x0 locals=0x58
0x0000 00000 (xxx.go:5) TEXT "".main(SB), ABIInternal, $88-0
0x0000 00000 (xxx.go:5) MOVQ (TLS), CX
0x0009 00009 (xxx.go:5) CMPQ SP, 16(CX)
0x000d 00013 (xxx.go:5) PCDATA $0, $-2
0x000d 00013 (xxx.go:5) JLS 267
0x0013 00019 (xxx.go:5) PCDATA $0, $-1
0x0013 00019 (xxx.go:5) SUBQ $88, SP
0x0017 00023 (xxx.go:5) MOVQ BP, 80(SP)
0x001c 00028 (xxx.go:5) LEAQ 80(SP), BP
0x0021 00033 (xxx.go:5) PCDATA $0, $-2
0x0021 00033 (xxx.go:5) PCDATA $1, $-2
0x0021 00033 (xxx.go:5) FUNCDATA $0, gclocals·69c1753bd5f81501d95132d08af04464(SB)
0x0021 00033 (xxx.go:5) FUNCDATA $1, gclocals·568470801006e5c0dc3947ea998fe279(SB)
0x0021 00033 (xxx.go:5) FUNCDATA $2, gclocals·bfec7e55b3f043d1941c093912808913(SB)
0x0021 00033 (xxx.go:5) FUNCDATA $3, "".main.stkobj(SB)
0x0021 00033 (xxx.go:6) PCDATA $0, $1
0x0021 00033 (xxx.go:6) PCDATA $1, $0
0x0021 00033 (xxx.go:6) LEAQ type.[2]int(SB), AX
0x0028 00040 (xxx.go:6) PCDATA $0, $0
0x0028 00040 (xxx.go:6) MOVQ AX, (SP)
0x002c 00044 (xxx.go:6) CALL runtime.newobject(SB)
0x0031 00049 (xxx.go:6) PCDATA $0, $1
0x0031 00049 (xxx.go:6) MOVQ 8(SP), AX
0x0036 00054 (xxx.go:6) MOVQ $5, (AX)
0x003d 00061 (xxx.go:6) MOVQ $6, 8(AX)
0x0045 00069 (xxx.go:7) PCDATA $0, $2
0x0045 00069 (xxx.go:7) LEAQ type.int(SB), CX
0x004c 00076 (xxx.go:7) PCDATA $0, $1
0x004c 00076 (xxx.go:7) MOVQ CX, (SP)
0x0050 00080 (xxx.go:7) PCDATA $0, $0
0x0050 00080 (xxx.go:7) MOVQ AX, 8(SP)
0x0055 00085 (xxx.go:7) MOVQ $2, 16(SP)
0x005e 00094 (xxx.go:7) MOVQ $2, 24(SP)
0x0067 00103 (xxx.go:7) MOVQ $4, 32(SP)
0x0070 00112 (xxx.go:7) CALL runtime.growslice(SB)
0x0075 00117 (xxx.go:7) PCDATA $0, $1
0x0075 00117 (xxx.go:7) MOVQ 40(SP), AX
0x007a 00122 (xxx.go:7) MOVQ 48(SP), CX
0x007f 00127 (xxx.go:7) MOVQ 56(SP), DX
0x0084 00132 (xxx.go:7) MOVQ $7, 16(AX)
0x008c 00140 (xxx.go:7) MOVQ $8, 24(AX)
0x0094 00148 (xxx.go:8) PCDATA $0, $0
0x0094 00148 (xxx.go:8) MOVQ AX, (SP)
0x0098 00152 (xxx.go:7) LEAQ 2(CX), AX
0x009c 00156 (xxx.go:8) MOVQ AX, 8(SP)
0x00a1 00161 (xxx.go:8) MOVQ DX, 16(SP)
0x00a6 00166 (xxx.go:8) CALL runtime.convTslice(SB)
... ...
我們可以看到:append 並沒有以獨立的身份出現在 CALL 彙編指令的後面,而是被換成:runtime.growslice、runtime.convTslice 以及相關彙編指令了。
“Gopher 部落” 知識星球開球了!高品質首發 Go 技術文章,“三天” 首發閱讀權,每年兩期 Go 語言發展現狀分析,每天提前 1 小時閱讀到新鮮的 Gopher 日報,網課、技術專欄、圖書內容前瞻,六小時內必答保證等滿足你關於 Go 語言生態的所有需求!星球首開,福利自然是少不了的!2020 年年底之前,8.8 折 (很吉利吧^_^) 加入星球,下方圖片掃起來吧!
我的 Go 技術專欄:“改善 Go 語⾔程式設計質量的 50 個有效實踐” 上線了,歡迎大家訂閱學習!
我的網課 “Kubernetes 實戰:高可用叢集搭建、配置、運維與應用” 在慕課網熱賣中,歡迎小夥伴們訂閱學習!
Gopher Daily(Gopher 每日新聞) 歸檔倉庫 - https://github.com/bigwhite/gopherdaily
我的聯絡方式:
- 微博:https://weibo.com/bigwhite20xx
- 微信公眾號:iamtonybai
- 部落格:tonybai.com
- github: https://github.com/bigwhite
- “Gopher 部落” 知識星球:https://public.zsxq.com/groups/51284458844544
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- GO語言————6.5 內建函式Go函式
- Python內建函式(一)Python函式
- 內建函式函式
- Vue原始碼: 建構函式入口Vue原始碼函式
- 10個你不一定知道的PHP內建函式PHP函式
- 訊息推送服務的技術挑戰難度在哪裡,線上直播原始碼告訴你原始碼
- webgl內建函式--通用函式Web函式
- 一文告訴你國外伺服器和國內伺服器的區別伺服器
- 一文告訴你大資料是什麼大資料
- 一文告訴你資料和資訊的區別
- vue原始碼解讀-建構函式Vue原始碼函式
- hive內建函式Hive函式
- MySQL 內建函式MySql函式
- js內建函式JS函式
- php 內建函式PHP函式
- python的部分內建函式Python函式
- webgl內建函式--指數函式Web函式
- python內建函式-eval()函式與exec()函式的區別Python函式
- python常用內建函式Python函式
- Python內建函式(二)Python函式
- 1.5.5 Python內建函式Python函式
- Python 內建函式大全Python函式
- python 常用內建函式Python函式
- Python內建函式示例Python函式
- 18. 內建函式函式
- python內建函式——sortedPython函式
- 幾個重要的內建函式函式
- Java 容器系列(七):HashMap 原始碼分析01之建構函式、內部類JavaHashMap原始碼函式
- webgl內建函式--幾何函式與矩陣函式Web函式矩陣
- webgl內建函式--向量函式與紋理查詢函式Web函式
- Hive函式(內建函式+自定義標準函式UDF)Hive函式
- office改不了pdf檔案,本文告訴你修改方法
- python內建函式 map/reducePython函式
- day13 內建函式函式
- python合集———內建函式合集Python函式
- 12.4、python內建函式—sortedPython函式
- 12.2、python內建函式—formatPython函式ORM
- c++內建函式物件C++函式物件