本篇內(nèi)容主要講解“Go的邊界檢查有哪些類型”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Go的邊界檢查有哪些類型”吧!
員工經(jīng)過(guò)長(zhǎng)期磨合與沉淀,具備了協(xié)作精神,得以通過(guò)團(tuán)隊(duì)的力量開發(fā)出優(yōu)質(zhì)的產(chǎn)品。創(chuàng)新互聯(lián)建站堅(jiān)持“專注、創(chuàng)新、易用”的產(chǎn)品理念,因?yàn)椤皩W⑺詫I(yè)、創(chuàng)新互聯(lián)網(wǎng)站所以易用所以簡(jiǎn)單”。公司專注于為企業(yè)提供成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、微信公眾號(hào)開發(fā)、電商網(wǎng)站開發(fā),重慶小程序開發(fā),軟件按需網(wǎng)站策劃等一站式互聯(lián)網(wǎng)企業(yè)服務(wù)。
邊界檢查,英文名 Bounds Check Elimination,簡(jiǎn)稱為 BCE。它是 Go 語(yǔ)言中防止數(shù)組、切片越界而導(dǎo)致內(nèi)存不安全的檢查手段。如果檢查下標(biāo)已經(jīng)越界了,就會(huì)產(chǎn)生 Panic。
邊界檢查使得我們的代碼能夠安全地運(yùn)行,但是另一方面,也使得我們的代碼運(yùn)行效率略微降低。
比如下面這段代碼,會(huì)進(jìn)行三次的邊界檢查
package main func f(s []int) { _ = s[0] // 檢查第一次 _ = s[1] // 檢查第二次 _ = s[2] // 檢查第三次 } func main() {}
你可能會(huì)好奇了,三次?我是怎么知道它要檢查三次的。
實(shí)際上,你只要在編譯的時(shí)候,加上參數(shù)即可,命令如下
$ go build -gcflags="-d=ssa/check_bce/debug=1" main.go # command-line-arguments ./main.go:4:7: Found IsInBounds ./main.go:5:7: Found IsInBounds ./main.go:6:7: Found IsInBounds
并不是所有的對(duì)數(shù)組、切片進(jìn)行索引操作都需要邊界檢查。
比如下面這個(gè)示例,就不需要進(jìn)行邊界檢查,因?yàn)榫幾g器根據(jù)上下文已經(jīng)得知,s 這個(gè)切片的長(zhǎng)度是多少,你的終止索引是多少,立馬就能判斷到底有沒(méi)有越界,因此是不需要再進(jìn)行邊界檢查,因?yàn)樵诰幾g的時(shí)候就已經(jīng)知道這個(gè)地方會(huì)不會(huì) panic。
package main func f() { s := []int{1,2,3,4} _ = s[:9] // 不需要邊界檢查 } func main() {}
因此可以得出結(jié)論,對(duì)于在編譯階段無(wú)法判斷是否會(huì)越界的索引操作才會(huì)需要邊界檢查,比如這樣子
package main func f(s []int) { _ = s[:9] // 需要邊界檢查 } func main() {}
在如下示例代碼中,由于索引 2 在最前面已經(jīng)檢查過(guò)會(huì)不會(huì)越界,因此聰明的編譯器可以推斷出后面的索引 0 和 1 不用再檢查啦
package main func f(s []int) { _ = s[2] // 檢查一次 _ = s[1] // 不會(huì)檢查 _ = s[0] // 不會(huì)檢查 } func main() {}
在下面這個(gè)示例中,可以在邏輯上保證不會(huì)越界的代碼,同樣是不會(huì)進(jìn)行越界檢查的。
package main func f(s []int) { for index, _ := range s { _ = s[index] _ = s[:index+1] _ = s[index:len(s)] } } func main() {}
在如下示例代碼中,雖然數(shù)組的長(zhǎng)度和容量可以確定,但是索引是通過(guò) rand.Intn() 函數(shù)取得的隨機(jī)數(shù),在編譯器看來(lái)這個(gè)索引值是不確定的,它有可能大于數(shù)組的長(zhǎng)度,也有可能小于數(shù)組的長(zhǎng)度。
因此第一次是需要進(jìn)行檢查的,有了第一次檢查后,第二次索引從邏輯上就能推斷,所以不會(huì)再進(jìn)行邊界檢查。
package main import ( "math/rand" ) func f() { s := make([]int, 3, 3) index := rand.Intn(3) _ = s[:index] // 第一次檢查 _ = s[index:] // 不會(huì)檢查 } func main() {}
但如果把上面的代碼稍微改一下,讓切片的長(zhǎng)度和容量變得不一樣,結(jié)果又會(huì)變得不一樣了。
package main import ( "math/rand" ) func f() { s := make([]int, 3, 5) index := rand.Intn(3) _ = s[:index] // 第一次檢查 _ = s[index:] // 第二次檢查 } func main() {}
我們只有當(dāng)數(shù)組的長(zhǎng)度和容量相等時(shí), :index 成立,才能一定能推出 index: 也成立,這樣的話,只要做一次檢查即可
一旦數(shù)組的長(zhǎng)度和容量不相等,那么 index 在編譯器看來(lái)是有可能大于數(shù)組長(zhǎng)度的,甚至大于數(shù)組的容量。
我們假設(shè) index 取得的隨機(jī)數(shù)為 4,那么它大于數(shù)組長(zhǎng)度,此時(shí) s[:index] 雖然可以成功,但是 s[index:] 是要失敗的,因此第二次邊界的檢查是有必要的。
你可能會(huì)說(shuō), index 不是最大值為 3 嗎?怎么可能是 4呢?
要知道編譯器在編譯的時(shí)候,并不知道 index 的最大值是 3 呢。
小結(jié)一下
當(dāng)數(shù)組的長(zhǎng)度和容量相等時(shí),s[:index] 成立能夠保證 s[index:] 也成立,因?yàn)橹灰獧z查一次即可
當(dāng)數(shù)組的長(zhǎng)度和容量不等時(shí),s[:index] 成立不能保證 s[index:] 也成立,因?yàn)橐獧z查兩次才可以
有了上面的鋪墊,再來(lái)看下面這個(gè)示例,由于數(shù)組是調(diào)用者傳入的參數(shù),所以編譯器的編譯的時(shí)候無(wú)法得知數(shù)組的長(zhǎng)度和容量是否相等,因此只能保險(xiǎn)一點(diǎn),兩個(gè)都檢查。
package main import ( "math/rand" ) func f(s []int, index int) { _ = s[:index] // 第一次檢查 _ = s[index:] // 第二次檢查 } func main() {}
但是如果把兩個(gè)表達(dá)式的順序反過(guò)來(lái),就只要做一次檢查就行了,原因我就不贅述了。
package main import ( "math/rand" ) func f(s []int, index int) { _ = s[index:] // 第一次檢查 _ = s[:index] // 不用檢查 } func main() {}
雖然編譯器已經(jīng)非常努力去消除一些應(yīng)該消除的邊界檢查,但難免會(huì)有一些遺漏。
這就需要"警民合作",對(duì)于那些編譯器還未考慮到的場(chǎng)景,但開發(fā)者又極力追求程序的運(yùn)行效率的,可以使用一些小技巧給出一些暗示,告訴編譯器哪些地方可以不用做邊界檢查。
比如下面這個(gè)示例,從代碼的邏輯上來(lái)說(shuō),是完全沒(méi)有必要做邊界檢查的,但是編譯器并沒(méi)有那么智能,實(shí)際上每個(gè)for循環(huán),它都要做一次邊界的檢查,非常的浪費(fèi)性能。
package main func f(is []int, bs []byte) { if len(is) >= 256 { for _, n := range bs { _ = is[n] // 每個(gè)循環(huán)都要邊界檢查 } } } func main() {}
可以試著在 for 循環(huán)前加上這么一句 is = is[:256] 來(lái)告訴編譯器新 is 的長(zhǎng)度為 256,最大索引值為 255,不會(huì)超過(guò) byte 的最大值,因?yàn)?is[n] 從邏輯上來(lái)說(shuō)是一定不會(huì)越界的。
package main func f(is []int, bs []byte) { if len(is) >= 256 { is = is[:256] for _, n := range bs { _ = is[n] // 不需要做邊界檢查 } } } func main() {}
到此,相信大家對(duì)“Go的邊界檢查有哪些類型”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!