<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>

          面試官:說說unsafe.Pointer和uintptr的區(qū)別和聯(lián)系

          共 5517字,需瀏覽 12分鐘

           ·

          2021-05-20 01:12

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

          前幾天 Go語言中文網(wǎng) 公眾號發(fā)了一篇文章:unsafe 真就不安全嗎?其中談到了 unsafe 包的初衷和功能。不過那篇文章還有一個功能沒講,那就是 unsafe.Pointer,今天就詳細(xì)介紹它,包括其他相關(guān)類型和實際使用情況。同時還會設(shè)計到社區(qū)對 unsafe 的看法以及建議。希望看完這篇文章,你能較好的掌握 unsafe.Pointer。當(dāng)然,請在必要時使用它。

          type Pointer

          此類型表示指向任意類型的指針,這意味著,unsafe.Pointer 可以轉(zhuǎn)換為任何類型或 uintptr 的指針值。你可能會想: 有什么限制嗎?沒有,是的... 你可以轉(zhuǎn)換 Pointer 為任何你想要的,但你必須處理可能的后果。為了減少可能出現(xiàn)的問題,你可以使用某些模式:

          以下涉及 Pointer 的模式是有效的。不使用這些模式的代碼今天可能無效,或者將來可能無效。即使是下面這些有效的模式,也帶有重要的警告。” —— golang.org

          你也可以使用 go vet,但是它不能解決所有的問題。因此,我建議你遵循這些模式,因為這是減少錯誤的唯一方法。

          快速拷貝

          如果兩種類型的內(nèi)存布局相同,為了避免內(nèi)存分配,你可以通過以下機(jī)制將類型 *T1 的指針轉(zhuǎn)換為類型 *T2 的指針,將類型 T1 的值復(fù)制到類型 T2 的變量中:

          ptrT1 := &T1{}
          ptrT2 = (*T2)(unsafe.Pointer(ptrT1))

          但是要小心,這種轉(zhuǎn)換是有代價的,現(xiàn)在兩個指針指向同一個內(nèi)存地址,所以每個指針的改變也會反應(yīng)到另一個指針上。可以通過這里驗證[1]

          unsafe.Pointer != uintptr

          我已經(jīng)提到過,指針可以轉(zhuǎn)換為 uintptr 并轉(zhuǎn)回來,但是轉(zhuǎn)回來是有一些特殊的條件限制的。unsafe.Pointer 是一個真正的指針,它不僅保持內(nèi)存地址,包括動態(tài)鏈接的地址,但 uintptr 只是一個數(shù)字,因此它更小,但有代價。如果你轉(zhuǎn)換 unsafe.Pointer 為 uintptr 后,指針不再引用指向的變量,而且在將 uintptr 轉(zhuǎn)換回 unsafe.Pointer 變量之前,垃圾收集器可以輕松地回收該內(nèi)存。至少有兩種解決方案可以避免此問題。第一個更復(fù)雜的,但也真正顯示了,為了使用 unsafe 包,你必須犧牲什么。有一個特殊的函數(shù),runtime.KeepAlive 可以避免 GC 不恰當(dāng)?shù)幕厥铡K犉饋砗軓?fù)雜,而且使用起來更加復(fù)雜。這里為你準(zhǔn)備了實際例子[2]

          指針?biāo)惴?/span>

          還有另一種方法避免 GC 不恰當(dāng)回收。即在同一個語句中做以下事情:將 unsafe.Poniter 轉(zhuǎn)為 uintptr,以及將 uintptr 做其他運算,最后轉(zhuǎn)回 unsafe.Pointer 。因為 uintptr 只是一個數(shù)字,我們可以做所有特殊的算術(shù)運算,比如加法或減法。我們?nèi)绾问褂盟恐羔標(biāo)惴ㄍㄟ^了解內(nèi)存布局和算術(shù)運算,可以得到任何需要的數(shù)據(jù)。讓我們來看看下一個例子:

          x := [4]byte{10111213}
          elPtr := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + 3*unsafe.Sizeof(x[0]))

          有了指向字節(jié)數(shù)組第一個元素的指針,我們就可以在不使用索引的情況下獲得最后一個元素。如果將指針移動三個字節(jié),我們就可以得到最后一個元素。

          因此,在一個表達(dá)式中執(zhí)行所有轉(zhuǎn)換可以省去 GC 清理的麻煩。上述三種模式說明了如何在不同情況下正確地轉(zhuǎn)換 unsafe.Pointer 為其他數(shù)據(jù)類型的指針。

          Syscalls

          在包 syscall 中,有一個函數(shù) syscall.Syscall 接收 uintptr 格式的指針的系統(tǒng)調(diào)用,我們可以通過 unsafe.Pointer 得到 uintptr。重要的是,你必須進(jìn)行正確的轉(zhuǎn)換:

          a := &A{1}
          b := &A{2}
          syscall.Syscall(0uintptr(unsafe.Pointer(a)), uintptr(unsafe.Pointer(b))) // Right

          aPtr := uintptr(unsafe.Pointer(a)
          bPtr := uintptr(unsafe.Pointer(b)
          syscall.Syscall(0, aPtr, bPtr) // Wrong

          reflect.Value.Pointer 和 reflect.Value.UnsafeAddr

          reflect 包中有兩個方法: Pointer 和 UnsafeAddr,它們返回 uintptr,因此我們應(yīng)該立即將結(jié)果轉(zhuǎn)換為 unsafe.Pointer,因為我們需要時刻“提防”我們的 GC 朋友:

          p1 := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer())) // Right

          ptr := reflect.ValueOf(new(int)).Pointer() // Wrong
          p2 := (*int)(unsafe.Pointer(ptr) // Wrong

          reflect.SliceHeader 和 reflect.StringHeader

          reflect 包中有兩種類型: SliceHeader 和 StringHeader,它們都具有字段 Data uintptr。正如你所記得的那樣,uintptr 通常與 unsafe.Pointer 聯(lián)系在一起,見下面代碼:

          var s string
          hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
          hdr.Data = uintptr(unsafe.Pointer(p))
          hdr.Len = n

          以上就是所有可能關(guān)于 unsafe.Pointer 使用的模式,所有不遵循這些模式或從這些模式派生的情況很可能是無效的。但是 unsafe 包不僅在代碼中而且在代碼之外都會帶來問題。讓我們回顧一下其中的幾個。

          兼容性

          Go 有兼容性指南[3],保證版本更新的兼容性。簡單地說,它保證你的代碼在升級后仍然可以工作,但是不能保證你已經(jīng)導(dǎo)入了 unsafe 的包。unsafe 包的使用可能會破壞你的代碼的每個版本: major,minor,甚至安全修補(bǔ)程序。所以在導(dǎo)入之前,試著想一下這樣一種情況:你的客戶問你為什么我們不能通過升級 Go 版本來消除漏洞,或者為什么在更新之后什么都不能工作了。

          不同的行為

          你知道所有的 Go 數(shù)據(jù)類型嗎?你聽說過 int 嗎?如果我們已經(jīng)有 int32 和 int64,為什么還有 int?實際上 int 類型是根據(jù)計算機(jī)體系結(jié)構(gòu)(x32 或 x64)將其轉(zhuǎn)換為 int32 或 int64 類型。所以請記住,unsafe 的函數(shù)結(jié)果和內(nèi)存布局在不同的架構(gòu)上可能是不同的,例如:

          var s string
          unsafe.Sizeof(s) // x32 上是 8,而 x64 上是 16

          社區(qū)的情況

          我想知道:如果這個包如此危險,有多少冒險者在使用它。我已經(jīng)在 GitHub[4] 上搜索過了。與 crypto[5]math[6] 相比,數(shù)量并不多。其中超過一半的內(nèi)容是關(guān)于使用 unsafe 的方法的技巧和可能的偏差,而不是一些真正的用法。

          Rust 社區(qū)有一個事件:一個叫 Nikolay Kim 的,他是 activex[7] 項目的創(chuàng)始人,在社區(qū)的巨大壓力下,將 activex 庫變成了私有。后來再公開該倉庫時,將其中一個貢獻(xiàn)者提升為所有者,然后離開[8]。所有這一切的發(fā)生都是因為一些人認(rèn)為使用了 unsafe 包,這太危險不應(yīng)該使用。我知道 Go 社區(qū)目前沒有這種情況,而且 Go 社區(qū)里也沒有唯一正確的觀點。我想要提醒的是,如果你在代碼中導(dǎo)入了 unsafe 的代碼,請做好準(zhǔn)備,社區(qū)可能會。。。

          愛好者

          有很多人和很多想法,這篇文章[9]展示了使用 int 和使用指針操作的新方法,簡而言之,它看起來像這樣:

          var foo int
          fooslice = (*[1]int)(unsafe.Pointer(&foo))[:]

          對此,我不發(fā)表意見,我只會提到,你應(yīng)該注意導(dǎo)入 unsafe 可能的問題。

          最后

          我個人試著去思考 unsafe 帶來問題的可能性,這里有一個使用 unsafe 的例子。假設(shè)你導(dǎo)入了一些執(zhí)行某些有用操作的第三方包,比如將 DB 客戶端對象和日志記錄器包裝到一個實體中,以使所有操作的日志記錄更加容易,或者像我的例子中那樣,導(dǎo)入一些返回對象的動物的函數(shù)...

          package main

          import (
           "fmt"
           "third-party/safelib"
          )

          func main() {
           a := safelib.NewA("https://google.com""1234"// Url and password
           fmt.Println("My spiritual animal is: ", safelib.DoSomeHipsterMagic(a))
           a.Show()
          }

          在這個函數(shù)中,我們將 interface{} 斷言為一些已知類型,并快速復(fù)制到一些 Malicious 類型,這些 Malicious 類型具有獲取和設(shè)置私有字段的方法,如 url 和密碼。所以這個包可以提取出所有有趣的數(shù)據(jù),甚至替換 url,這樣下次你嘗試連接到 DB 時,有人會獲得你的憑證。

          func DoSomeHipsterMagic(any interface{}) string {
           if a, ok := any.(*A); ok {
            mal := (*Malicious)(unsafe.Pointer(a))
            mal.setURL("http://hacker.com")
           }

           return "Cool green dragon, arrh ??"
          }

          最后的最后,切記所有的技術(shù)都有一定的代價,但是 unsafe 技術(shù)尤其“昂貴”,所以我的建議是在使用它之前要三思。

          參考資料

          [1]

          這里驗證: https://play.studygolang.com/p/bZGEHrHp4LM

          [2]

          實際例子: https://play.studygolang.com/p/L7rgheqNo9w

          [3]

          兼容性指南: https://docs.studygolang.com/doc/go1compat

          [4]

          GitHub: https://github.com/search?l=Go&q=unsafe&type=Repositories

          [5]

          crypto: https://github.com/search?l=Go&q=crypto&type=Repositories

          [6]

          math: https://github.com/search?l=Go&q=math&type=Repositories

          [7]

          activex: https://github.com/actix

          [8]

          離開: https://github.com/actix/actix-web/issues/1289

          [9]

          這篇文章: https://nullprogram.com/blog/2019/06/30/



          推薦閱讀


          福利

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


          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  豆花性视频 | 国产精品女人18水真多 | 91av影院在线观看 | 骚碰人人看 | 78视频入口 |