<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 是如何確保內(nèi)存安全的?

          共 3639字,需瀏覽 8分鐘

           ·

          2020-11-07 07:08

          點(diǎn)擊上方藍(lán)色“Go語(yǔ)言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

          Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

          ??這篇文章基于 Go 1.13 編寫。

          Go 的一系列內(nèi)存管理手段(內(nèi)存分配,垃圾回收,內(nèi)存訪問檢查)使許多開發(fā)者的開發(fā)工作變得很輕松。編譯器通過在代碼中引入“邊界檢查” 來確保安全地訪問內(nèi)存。

          生成的指令

          Go 引入了一些控制點(diǎn)位,來確保我們的程序訪問的內(nèi)存片段安全且有效的。讓我們從一個(gè)簡(jiǎn)單的例子開始:

          package?main

          func?main()?{
          ????list?:=?[]int{1,?2,?3}

          ????printList(list)
          }

          func?printList(list?[]int)?{
          ????println(list[2])
          ????println(list[3])
          }

          這段代碼跑起來之后會(huì) panic:

          3
          panic:?runtime?error:?index?out?of?range?[3]?with?length?3

          Go 通過添加邊界檢查來防止不正確的內(nèi)存訪問

          如果你想知道沒有這些檢查會(huì)怎么樣,你可以使用 -gcflags="-B" 的選項(xiàng),輸出如下

          3
          824633993168

          因?yàn)檫@塊內(nèi)存是無(wú)效的,它會(huì)讀取不屬于這個(gè) slice 的下一個(gè) bytes。

          利用命令 go tool compile -S main.go 來生成對(duì)應(yīng)的匯編[1]代碼,就可以看到這些檢查點(diǎn):

          0x0021?00033?(main.go:10)??MOVQ???"".list+48(SP),?CX
          0x0026?00038?(main.go:10)??CMPQ???CX,?$2
          0x002a?00042?(main.go:10)??JLS????161
          [...]?here?Go?prints?the?third?element
          0x0057?00087?(main.go:11)??MOVQ???"".list+48(SP),?CX
          0x005c?00092?(main.go:11)??CMPQ???CX,?$3
          0x0060?00096?(main.go:11)??JLS????151
          [...]
          0x0096?00150?(main.go:12)??RET
          0x0097?00151?(main.go:11)??MOVL???$3,?AX
          0x009c?00156?(main.go:11)??CALL???runtime.panicIndex(SB)
          0x00a1?00161?(main.go:10)??MOVL???$2,?AX
          0x00a6?00166?(main.go:10)??CALL???runtime.panicIndex(SB)

          Go 先使用 MOVQ 指令將 list 變量的長(zhǎng)度放入寄存器 CX

          0x0021?00033?(main.go:10)??MOVQ???"".list+48(SP),?CX

          友情提醒,slice 類型的變量由三部分組成,指向底層數(shù)組的指針、長(zhǎng)度,容量(capacity)。list 變量在棧中的位置如下圖:

          通過將棧指針移動(dòng) 48 個(gè)字節(jié)就可以訪問長(zhǎng)度

          下一條指令將 slice 的長(zhǎng)度與程序即將訪問的偏移量進(jìn)行比較

          CMPQ 指令會(huì)將兩個(gè)值相減,并在下一條指令中與 0 進(jìn)行比較。如果 slice 的長(zhǎng)度(寄存器 CX)減去要訪問的偏移量(在這個(gè)例子當(dāng)中是 2)小于或等于 0(JLSJump on lower or the same 的縮寫),程序就會(huì)跳到 161 處繼續(xù)執(zhí)行。

          兩種邊界檢查使用的都是相同的指令。除了看生成的匯編代碼,Go 提供了一個(gè)編譯期的通行證去打印出邊界檢查的點(diǎn),你可以在 buildrun 的時(shí)候使用標(biāo)志 -gcflags="-d=ssa/check_bce/debug=1" 去開啟。輸出如下:

          ./main.go:10:14:?Found?IsInBounds
          ./main.go:11:14:?Found?IsInBounds

          我們可以看到輸出里生成了兩個(gè)檢查點(diǎn)。不過 Go 編譯器足夠聰明,在不需要的情況下,它不會(huì)生成邊界檢查的指令。

          規(guī)則

          在每次訪問內(nèi)存的時(shí)候都生成檢查指令是非常低效的,讓我們稍微修改一下前面的例子。

          package?main

          func?main()?{
          ????list?:=?[]int{1,?2,?3}

          ????printList(list)
          }

          func?printList(list?[]int)?{
          ????println(list[3])
          ????println(list[2])
          }

          兩個(gè) println 指令對(duì)調(diào)了,用 check_bce 標(biāo)志再去跑一遍程序,這次只有一處邊界檢查:

          ./main.go:11:14:?Found?IsInBounds

          程序先檢查了偏移量 3 。如果是有效的,那么 2 很明顯也是有效的,沒必要再去檢查了。可以通過命令 GOSSAFUNC=printList Go run main.go 來生成 SSA 代碼看編譯過程。這張圖就是生成的帶邊界檢查的 SSA 代碼:

          里面的 prove pass 將邊界檢查標(biāo)記為移除,這樣后面的 pass 將會(huì)收集這些 dead code:

          用這條命令 GOSSAFUNC=printList Go run -gcflags="-d=ssa/prove/debug=3" main.go 可以把 pass 背后的邏輯打印出來,它也會(huì)生成 SSA 文件來幫助你 debug,接下來看命令的輸出:

          這個(gè) pass 實(shí)際上會(huì)采取不同的策略,并建立了 fact 表。這些 fact 決定了矛盾點(diǎn)在哪里。在我們這個(gè)例子里,我們可以通過 SSA 的 pass 來解讀這些規(guī)則:

          第一個(gè)階段從代表指令 println(list[3]) 的分析塊 b1 開始,這個(gè)指令有兩種可能:

          • 偏移量 [3] 在邊界中,跳到第二個(gè)指令 b2。在這個(gè)例子中,Go 指定 v7 的限制(slice 的長(zhǎng)度)是 [4, max(int)]
          • 偏移量 [3 不在邊界中, 程序跳轉(zhuǎn)到 b3 指令并 panic。

          接下來,Go 開始處理 b2 塊(第二個(gè)指令)。這里也有兩種可能

          • 偏移量 [2] 在邊界中,這意味著 slice 的長(zhǎng)度 v7v23(偏移量 [2]) 要大。在先前的 b1 塊中 Go 已經(jīng)判斷了 v7 > 4, 所以這個(gè)已經(jīng)被確認(rèn)了。
          • 偏移量 [2] 不在邊界中,這意味著它比 slice 的長(zhǎng)度 v7 更大,但 v7 的限制是 [4, max(int)] ,所以 Go 會(huì)將這個(gè)分之標(biāo)記為矛盾,意味著這種情況永遠(yuǎn)不會(huì)發(fā)生,這條指令的邊界檢查可以被移除。

          這個(gè) pass 在隨著時(shí)間不斷地改善,現(xiàn)在可以參考更多的 case[2]。消除邊界檢查可以略微提升 Go 程序的運(yùn)行速度,但除非你的程序是微妙級(jí)敏感的,不然沒有必要去優(yōu)化它。


          via: https://medium.com/a-journey-with-go/go-memory-safety-with-bounds-check-1397bef748b5

          作者: Vincent Blanchon[3]譯者:yxlimo[4]校對(duì):Alex.Jiang[5]本文由 GCTT[6] 原創(chuàng)編譯,Go 中文網(wǎng)[7] 榮譽(yù)推出

          參考資料

          [1]

          匯編: https://golang.org/doc/asm

          [2]

          更多的 case: https://github.com/golang/go/blob/master/test/prove.go

          [3]

          Vincent Blanchon: https://medium.com/@blanchon.vincent

          [4]

          yxlimo: https://github.com/yxlimo

          [5]

          Alex.Jiang: https://github.com/JYSDeveloper

          [6]

          GCTT: https://github.com/studygolang/GCTT

          [7]

          Go 中文網(wǎng): https://studygolang.com/



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。


          瀏覽 99
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品九九九九九 | 日本在线精品视频 | 五月色婷| 中文无码在线观看中文字幕av中文 | sm在线观看 |