slice 转byte 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 mainimport ("fmt""unsafe" )type int int8func main() {var a int = 5fmt.Printf("%Tn", a) // main.int,而不是intfmt.Println(unsafe.Sizeof(a)) // 1,而不是8 }在上述這個源文件中,預定義標識符int被override為一個自定義類型int,該類型的underlying type為int8,于是當我們輸出該類型變量(代碼中的變量a)的類型和長度時,我們得到的是http://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 intint is a signed integer type that is at least 32 bits in size. It is adistinct type, however, and not an alias for, say, int32.func cap(v Type) int func copy(dst, src []Type) int func len(v Type) int4. 內建函數的源碼在哪里?
作為預聲明標識符子集的內建函數們在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 mainimport "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=0x580x0000 00000 (xxx.go:5) TEXT "".main(SB), ABIInternal, $88-00x0000 00000 (xxx.go:5) MOVQ (TLS), CX0x0009 00009 (xxx.go:5) CMPQ SP, 16(CX)0x000d 00013 (xxx.go:5) PCDATA $0, $-20x000d 00013 (xxx.go:5) JLS 2670x0013 00019 (xxx.go:5) PCDATA $0, $-10x0013 00019 (xxx.go:5) SUBQ $88, SP0x0017 00023 (xxx.go:5) MOVQ BP, 80(SP)0x001c 00028 (xxx.go:5) LEAQ 80(SP), BP0x0021 00033 (xxx.go:5) PCDATA $0, $-20x0021 00033 (xxx.go:5) PCDATA $1, $-20x0021 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, $10x0021 00033 (xxx.go:6) PCDATA $1, $00x0021 00033 (xxx.go:6) LEAQ type.[2]int(SB), AX0x0028 00040 (xxx.go:6) PCDATA $0, $00x0028 00040 (xxx.go:6) MOVQ AX, (SP)0x002c 00044 (xxx.go:6) CALL runtime.newobject(SB)0x0031 00049 (xxx.go:6) PCDATA $0, $10x0031 00049 (xxx.go:6) MOVQ 8(SP), AX0x0036 00054 (xxx.go:6) MOVQ $5, (AX)0x003d 00061 (xxx.go:6) MOVQ $6, 8(AX)0x0045 00069 (xxx.go:7) PCDATA $0, $20x0045 00069 (xxx.go:7) LEAQ type.int(SB), CX0x004c 00076 (xxx.go:7) PCDATA $0, $10x004c 00076 (xxx.go:7) MOVQ CX, (SP)0x0050 00080 (xxx.go:7) PCDATA $0, $00x0050 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, $10x0075 00117 (xxx.go:7) MOVQ 40(SP), AX0x007a 00122 (xxx.go:7) MOVQ 48(SP), CX0x007f 00127 (xxx.go:7) MOVQ 56(SP), DX0x0084 00132 (xxx.go:7) MOVQ $7, 16(AX)0x008c 00140 (xxx.go:7) MOVQ $8, 24(AX)0x0094 00148 (xxx.go:8) PCDATA $0, $00x0094 00148 (xxx.go:8) MOVQ AX, (SP)0x0098 00152 (xxx.go:7) LEAQ 2(CX), AX0x009c 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以及相關匯編指令了。
Go技術專欄“改善Go語?編程質量的50個有效實踐”主要滿足廣大gopher關于Go語言進階的需求,圍繞如何寫出地道且高質量Go代碼給出50條有效實踐建議,歡迎大家訂閱!
Gopher Daily(Gopher每日新聞)歸檔倉庫 - https://github.com/bigwhite/gopherdaily
我的聯系方式:
- 微博:https://weibo.com/bigwhite20xx
- 微信公眾號:iamtonybai
- 博客:http://tonybai.com
- github: https://github.com/bigwhite
- “Gopher部落”知識星球:https://public.zsxq.com/groups/51284458844544
總結
以上是生活随笔為你收集整理的slice 转byte go_一文告诉你神奇的Go内建函数源码在哪里的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构--链表--单链表中环的检测,环
- 下一篇: linux shell 输出日期格式,L