溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go的邊界檢查有哪些類型

發(fā)布時(shí)間:2021-10-14 11:33:12 來(lái)源:億速云 閱讀:148 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“Go的邊界檢查有哪些類型”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Go的邊界檢查有哪些類型”吧!

1. 什么是邊界檢查?

邊界檢查,英文名 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

2. 邊界檢查的條件?

并不是所有的對(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()  {}

3. 邊界檢查的特殊案例

3.1 案例一

在如下示例代碼中,由于索引 2 在最前面已經(jīng)檢查過(guò)會(huì)不會(huì)越界,因此聰明的編譯器可以推斷出后面的索引 0 和 1 不用再檢查啦

 package main  func f(s []int) {     _ = s[2] // 檢查一次     _ = s[1]  // 不會(huì)檢查     _ = s[0]  // 不會(huì)檢查 }  func main() {}

3.2 案例二

在下面這個(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()  {}

3.3 案例三

在如下示例代碼中,雖然數(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查兩次才可以

3.4 案例四

有了上面的鋪墊,再來(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()  {}

5. 主動(dòng)消除邊界檢查

雖然編譯器已經(jīng)非常努力去消除一些應(yīng)該消除的邊界檢查,但難免會(huì)有一些遺漏。

這就需要"警民合作",對(duì)于那些編譯器還未考慮到的場(chǎng)景,但開(kāi)發(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í)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

go
AI