成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

這 Go 的邊界檢查,簡直讓人抓狂~

開發 后端
邊界檢查,英文名 Bounds Check Elimination,簡稱為 BCE。它是 Go 語言中防止數組、切片越界而導致內存不安全的檢查手段。如果檢查下標已經越界了,就會產生 Panic。

 [[392406]]

1. 什么是邊界檢查?

邊界檢查,英文名 Bounds Check Elimination,簡稱為 BCE。它是 Go 語言中防止數組、切片越界而導致內存不安全的檢查手段。如果檢查下標已經越界了,就會產生 Panic。

邊界檢查使得我們的代碼能夠安全地運行,但是另一方面,也使得我們的代碼運行效率略微降低。

比如下面這段代碼,會進行三次的邊界檢查

  1. package main 
  2.  
  3. func f(s []int) { 
  4.     _ = s[0]  // 檢查第一次 
  5.     _ = s[1]  // 檢查第二次 
  6.     _ = s[2]  // 檢查第三次 
  7.  
  8. func main() {} 

你可能會好奇了,三次?我是怎么知道它要檢查三次的。

實際上,你只要在編譯的時候,加上參數即可,命令如下

  1. $ go build -gcflags="-d=ssa/check_bce/debug=1" main.go 
  2. # command-line-arguments 
  3. ./main.go:4:7: Found IsInBounds 
  4. ./main.go:5:7: Found IsInBounds 
  5. ./main.go:6:7: Found IsInBounds 

2. 邊界檢查的條件?

并不是所有的對數組、切片進行索引操作都需要邊界檢查。

比如下面這個示例,就不需要進行邊界檢查,因為編譯器根據上下文已經得知,s 這個切片的長度是多少,你的終止索引是多少,立馬就能判斷到底有沒有越界,因此是不需要再進行邊界檢查,因為在編譯的時候就已經知道這個地方會不會 panic。

  1. package main 
  2.  
  3. func f() { 
  4.     s := []int{1,2,3,4} 
  5.     _ = s[:9]  // 不需要邊界檢查 
  6. func main()  {} 

因此可以得出結論,對于在編譯階段無法判斷是否會越界的索引操作才會需要邊界檢查,比如這樣子

  1. package main 
  2.  
  3.  
  4. func f(s []int) { 
  5.     _ = s[:9]  // 需要邊界檢查 
  6. func main()  {} 

3. 邊界檢查的特殊案例

3.1 案例一

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

  1.  package main 
  2.  
  3. func f(s []int) { 
  4.     _ = s[2] // 檢查一次 
  5.     _ = s[1]  // 不會檢查 
  6.     _ = s[0]  // 不會檢查 
  7.  
  8. func main() {} 

3.2 案例二

在下面這個示例中,可以在邏輯上保證不會越界的代碼,同樣是不會進行越界檢查的。

  1. package main 
  2.  
  3. func f(s []int) { 
  4.     for index, _ := range s { 
  5.         _ = s[index
  6.         _ = s[:index+1] 
  7.         _ = s[index:len(s)] 
  8.     } 
  9.  
  10. func main()  {} 

3.3 案例三

在如下示例代碼中,雖然數組的長度和容量可以確定,但是索引是通過 rand.Intn() 函數取得的隨機數,在編譯器看來這個索引值是不確定的,它有可能大于數組的長度,也有可能小于數組的長度。

因此第一次是需要進行檢查的,有了第一次檢查后,第二次索引從邏輯上就能推斷,所以不會再進行邊界檢查。

  1. package main 
  2.  
  3. import ( 
  4.     "math/rand" 
  5.  
  6. func f()  { 
  7.     s := make([]int, 3, 3) 
  8.     index := rand.Intn(3) 
  9.      _ = s[:index]  // 第一次檢查 
  10.     _ = s[index:]  // 不會檢查 
  11.  
  12. func main()  {} 

但如果把上面的代碼稍微改一下,讓切片的長度和容量變得不一樣,結果又會變得不一樣了。

  1. package main 
  2.  
  3. import ( 
  4.     "math/rand" 
  5.  
  6. func f()  { 
  7.     s := make([]int, 3, 5) 
  8.     index := rand.Intn(3) 
  9.      _ = s[:index]  // 第一次檢查 
  10.     _ = s[index:]  // 第二次檢查 
  11.  
  12. func main()  {} 

我們只有當數組的長度和容量相等時, :index 成立,才能一定能推出 index: 也成立,這樣的話,只要做一次檢查即可

一旦數組的長度和容量不相等,那么 index 在編譯器看來是有可能大于數組長度的,甚至大于數組的容量。

我們假設 index 取得的隨機數為 4,那么它大于數組長度,此時 s[:index] 雖然可以成功,但是 s[index:] 是要失敗的,因此第二次邊界的檢查是有必要的。

你可能會說, index 不是最大值為 3 嗎?怎么可能是 4呢?

要知道編譯器在編譯的時候,并不知道 index 的最大值是 3 呢。

小結一下

當數組的長度和容量相等時,s[:index] 成立能夠保證 s[index:] 也成立,因為只要檢查一次即可

當數組的長度和容量不等時,s[:index] 成立不能保證 s[index:] 也成立,因為要檢查兩次才可以

3.4 案例四

有了上面的鋪墊,再來看下面這個示例,由于數組是調用者傳入的參數,所以編譯器的編譯的時候無法得知數組的長度和容量是否相等,因此只能保險一點,兩個都檢查。

  1. package main 
  2.  
  3. import ( 
  4.     "math/rand" 
  5.  
  6. func f(s []intindex int) { 
  7.     _ = s[:index] // 第一次檢查 
  8.     _ = s[index:] // 第二次檢查 
  9.  
  10. func main()  {} 

但是如果把兩個表達式的順序反過來,就只要做一次檢查就行了,原因我就不贅述了。

  1. package main 
  2.  
  3. import ( 
  4.     "math/rand" 
  5.  
  6. func f(s []intindex int) { 
  7.     _ = s[index:] // 第一次檢查 
  8.     _ = s[:index] // 不用檢查 
  9.  
  10. func main()  {} 

5. 主動消除邊界檢查

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

這就需要"警民合作",對于那些編譯器還未考慮到的場景,但開發者又極力追求程序的運行效率的,可以使用一些小技巧給出一些暗示,告訴編譯器哪些地方可以不用做邊界檢查。

比如下面這個示例,從代碼的邏輯上來說,是完全沒有必要做邊界檢查的,但是編譯器并沒有那么智能,實際上每個for循環,它都要做一次邊界的檢查,非常的浪費性能。

  1. package main 
  2.  
  3.  
  4. func f(is []int, bs []byte) { 
  5.     if len(is) >= 256 { 
  6.         for _, n := range bs { 
  7.             _ = is[n] // 每個循環都要邊界檢查 
  8.         } 
  9.     } 
  10. func main()  {} 

可以試著在 for 循環前加上這么一句 is = is[:256] 來告訴編譯器新 is 的長度為 256,最大索引值為 255,不會超過 byte 的最大值,因為 is[n] 從邏輯上來說是一定不會越界的。

  1. package main 
  2.  
  3.  
  4. func f(is []int, bs []byte) { 
  5.     if len(is) >= 256 { 
  6.         is = is[:256] 
  7.         for _, n := range bs { 
  8.             _ = is[n] // 不需要做邊界檢查 
  9.         } 
  10.     } 
  11. func main()  {} 

6. 寫在最后

本文上面列出的例子并沒有涵蓋標準編譯器支持的所有邊界檢查消除的情形。本文列出的僅僅是一些常見的情形。

盡管標準編譯器中的邊界檢查消除特性依然不是100%完美,但是對很多常見的情形,它確實很有效。自從標準編譯器支持此特性以來,在每個版本更新中,此特性都在不斷地改進增強。無需質疑,在以后的版本中,標準編譯器會更加得智能,以至于上面第5個例子中提供給編譯器的暗示有可能將變得不再必要。謝謝Go語言開發團隊出色的工作!

7. 參考文檔

https://gfw.go101.org/article/bounds-check-elimination.html

本文轉載自微信公眾號「Go編程時光」,作者寫代碼的明哥。轉載本文請聯系Go編程時光公眾號。   

 

責任編輯:武曉燕 來源: Go編程時光
相關推薦

2022-02-08 19:33:13

技巧代碼格式

2012-09-04 09:55:22

代碼抓狂的代碼開發

2013-09-12 15:39:30

編程語言BANCStar

2018-10-18 09:41:41

2019-09-16 09:49:49

數據庫數據結構SQL

2021-09-08 05:43:28

網盤硬盤軟件

2025-05-26 09:31:23

2023-09-25 08:17:36

2025-04-22 11:10:00

2025-03-03 00:00:00

2019-11-12 13:39:35

電腦中央處理器軟件

2017-08-28 21:02:55

深度學習神經網絡

2020-12-08 11:02:28

黑客攻擊網絡

2024-02-26 13:39:55

2024-09-18 16:02:19

2024-01-15 07:05:50

開發大事務數據庫

2020-10-06 18:28:52

gosecGo代碼安全

2022-07-29 09:12:44

軟件硬件開發

2025-06-03 08:10:52

2020-12-02 16:40:00

微信新功能移動應用
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品国产欧美一区二区成人 | 日本视频在线 | 亚洲欧美少妇 | 国产成人综合久久 | av一级毛片 | a在线观看 | 精品欧美久久 | 亚洲高清在线视频 | 久久亚洲欧美日韩精品专区 | 在线免费看黄 | 中文成人在线 | 国产免费一区 | 天天操天天插天天干 | 欧美日韩在线观看视频网站 | 久久久久久99 | 日日夜夜精品视频 | 久久丝袜| 丁香久久| 亚洲 中文 欧美 日韩 在线观看 | 国产传媒毛片精品视频第一次 | 欧美国产精品久久久 | 国产精品色哟哟网站 | 色欧美综合| 日韩精品一区二区三区在线播放 | 色在线免费视频 | 亚洲国产成人av好男人在线观看 | 久久久人成影片一区二区三区 | 黄 色 毛片免费 | 国产特级毛片 | 久久国产日本 | 国产激情一区二区三区 | 国产在线视频99 | 国产成人精品a视频一区www | 成人黄色电影免费 | 成人欧美| 91视频一区二区 | 国产精品视频免费观看 | 男女爱爱网站 | 国产精品视频一 | 伊人天堂网 | 国产精品久久久久久久久图文区 |