溫馨提示×

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

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

gopl 底層編程(unsafe包)

發(fā)布時(shí)間:2020-07-10 05:18:10 來(lái)源:網(wǎng)絡(luò) 閱讀:2531 作者:騎士救兵 欄目:編程語(yǔ)言

包 unsafe 廣泛使用在和操作系統(tǒng)交互的低級(jí)包中, 例如 runtime、os、syscall、net 等,但是普通程序是不需要使用它的。

unsafe.Sizeof、Alignof 和 Offsetof

函數(shù) unsafe.Sizeof 報(bào)告?zhèn)鬟f給它的參數(shù)在內(nèi)存中占用的字節(jié)(Byte)長(zhǎng)度(1Byte=8bit,1個(gè)字節(jié)是8位),參數(shù)可以是任意類型的表達(dá)式,但它不會(huì)對(duì)表達(dá)式進(jìn)行求值。對(duì) Sizeof 的調(diào)用會(huì)返回一個(gè) uintptr 類型的常量表達(dá)式,所以返回的結(jié)果可以作為數(shù)組類型的長(zhǎng)度大小,或者用作計(jì)算其他的常量:

fmt.Println(unsafe.Sizeof(float64(0))) // "8"
fmt.Println(unsafe.Sizeof(uint8(0))) // "1"

函數(shù) Sizeof 僅報(bào)告每個(gè)數(shù)據(jù)結(jié)構(gòu)固定部分的內(nèi)存占用的字節(jié)長(zhǎng)度。以字符串為例,報(bào)告的只是字符串對(duì)應(yīng)的指針的字節(jié)長(zhǎng)度,而不是字符串內(nèi)容的長(zhǎng)度:

func main() {
    var x string
    x = "a"
    fmt.Println(unsafe.Sizeof(x), len(x)) // "16 1"

    var s []string
    for i := 0; i < 10000; i++ {
        s = append(s, "Hello")
    }
    x = strings.Join(s, ", ")
    fmt.Println(unsafe.Sizeof(x), len(x)) // "16 69998"
}

無(wú)論字符串多長(zhǎng),unsafe.Sizeof 返回的大小總是一樣的。

Go 語(yǔ)言中非聚合類型通常有一個(gè)固定的大小,盡管在不同工具鏈下生成的實(shí)際大小可能會(huì)有所不同??紤]到可移植性,引用類型或包含引用類型的大小都是1個(gè)字(word),轉(zhuǎn)換為字節(jié)數(shù),在32位系統(tǒng)上是4個(gè)字節(jié),在64位系統(tǒng)上是8個(gè)字節(jié)。

類型 大小
bool 1個(gè)字節(jié)
intN, uintN, floatN, complexN N/8個(gè)字節(jié)(例如float64是8個(gè)字節(jié))
int, uint, uintptr 1個(gè)字
*T 1個(gè)字
string 2個(gè)字(data,len)
[]T 3個(gè)字(data,len,cap)
map 1個(gè)字
func 1個(gè)字
chan 1個(gè)字
interface 2個(gè)字(type,value)

內(nèi)存對(duì)齊

在類型的值在內(nèi)存中對(duì)齊的情況下,計(jì)算機(jī)的加載或者寫(xiě)入會(huì)很高效。例如,int16的大小是2字節(jié)地址應(yīng)該是偶數(shù),rune類型的大小是4字節(jié)地址應(yīng)該是4的倍數(shù),float64、uint64 或 64位指針的大小是8字節(jié)地址應(yīng)該是8的倍數(shù)。對(duì)于更大倍數(shù)的地址對(duì)齊是不需要的,即使是complex128等較大的數(shù)據(jù)類型最多也只是8字節(jié)對(duì)齊。

結(jié)構(gòu)體的內(nèi)存對(duì)齊
因此,聚合類型(結(jié)構(gòu)體或數(shù)組)的值的長(zhǎng)度至少是它的成員或元素的長(zhǎng)度之和。并且由于“內(nèi)存間隙”的存在,可能還會(huì)更大一些。內(nèi)存空位是由編譯器添加的未使用的內(nèi)存地址,用來(lái)確保連續(xù)的成員或元素相對(duì)于結(jié)構(gòu)體或數(shù)組的起始地址是對(duì)齊的。
語(yǔ)言規(guī)范不要求結(jié)構(gòu)體成員聲明的順序?qū)?yīng)內(nèi)存中的布局順序,所以在理論上,編譯器可以自由安排,但實(shí)際上并沒(méi)有這么做。如果結(jié)構(gòu)體成員的類型是不同的,不同的排列順序可能使得結(jié)構(gòu)體占用的內(nèi)存不同。比如下面的三個(gè)結(jié)構(gòu)體擁有相同的成員,但是第一種寫(xiě)法比其他兩個(gè)定義需要占更多內(nèi)存:

                                              // 64-bit    32-bit
struct{ bool; float64; int16 } // 3 words 4words
struct{ float64; int16; bool } // 2 words 3words
struct{ bool; int16; float64 } // 2 words 3words

對(duì)齊算法太底層了(雖然貌似也沒(méi)有特別難),但確實(shí)不值得擔(dān)心每個(gè)結(jié)構(gòu)體的內(nèi)存布局,不過(guò)高效排列可以使數(shù)據(jù)結(jié)構(gòu)更加緊湊。一個(gè)容易掌握的建議是,將相同類型的成員定義在一起有可能更節(jié)約內(nèi)存空間。

另兩個(gè)函數(shù)

函數(shù) unsafe.Alignof 報(bào)告它參數(shù)類型所要求的對(duì)齊方式。和 Sizeof 一樣,它的參數(shù)可以是任意類型的表達(dá)式,并且返回一個(gè)常量。通常情況下布爾和數(shù)值類型對(duì)齊到它們的長(zhǎng)度(最多8個(gè)字節(jié)), 其它的類型則按字(word)對(duì)齊。

函數(shù) unsafe.Offsetof,參數(shù)必須是結(jié)構(gòu)體 x 的一個(gè)字段 x.f。函數(shù)返回 f 相對(duì)于結(jié)構(gòu)體 x 起始地址的偏移值,如果有內(nèi)存空位,也會(huì)計(jì)算在內(nèi)。

雖然這幾個(gè)函數(shù)在不安全的unsafe包里,但是這幾個(gè)函數(shù)是安全的,特別在需要優(yōu)化內(nèi)存空間時(shí)它們返回的結(jié)果對(duì)于理解原生的內(nèi)存布局很有幫助。

unsafe.Pointer

很多指針類型都寫(xiě)做 *T,意思是“一個(gè)指向T類型變量的指針”。unsafe.Pointer 類型是一種特殊類型的指針,它可以存儲(chǔ)任何變量的地址。這里不可以直接通過(guò) *P 來(lái)獲取 unsafe.Pointer 指針指向的那個(gè)變量的值,因?yàn)椴⒉恢雷兞康木唧w類型。和普通的指針一樣,unsafe.Pointer 類型的指針是可比較的并且可以和 nil 做比較,nil 是指針類型的零值。

查看浮點(diǎn)類型的位模式

一個(gè)普通的指針 *T 可以轉(zhuǎn)換為 unsafe.Pointer 類型的指針,并且一個(gè) unsafe.Pointer 類型的指針也可以轉(zhuǎn)換回普通的指針,被轉(zhuǎn)換回普通指針的類型不需要和原來(lái)的 *T 類型相同。這里有一個(gè)簡(jiǎn)單的應(yīng)用場(chǎng)景,先將 *float64 類型指針轉(zhuǎn)化為 *uint64 然后再把內(nèi)存中的值打印出來(lái)。這時(shí)候就是按照 uint64 類型來(lái)把值打印出來(lái),這樣就可以看到浮點(diǎn)類型的變量在內(nèi)存中的位模式:

func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }

func main() {
    fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
}

修改結(jié)構(gòu)體成員的值

很多 unsafe.Pointer 類型的值都是從普通指針到原始內(nèi)存地址以及再?gòu)膬?nèi)存地址到普通指針進(jìn)行轉(zhuǎn)換的中間值。下面的例子獲取變量 x 的地址,然后加上其成員 b 的地址偏移量,并將結(jié)果轉(zhuǎn)換為 *int16 指針類型,接著通過(guò)這個(gè)指針更新 x.b 的值:

var x struct {
    a bool
    b int16
    c []int
}

func main() {
    // 等價(jià)于 pb := &x.b ,但是這里是通過(guò)結(jié)構(gòu)體的地址加上字段的偏移量計(jì)算后獲取到的
    pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
    *pb = 42
    fmt.Println(x.b)
}

這里首先獲取到結(jié)構(gòu)體的地址,然后是成員的偏移量,相加后就是這個(gè)成員的內(nèi)存地址。因?yàn)檫@里知道該地址指向的數(shù)據(jù)類型,所以直接用一個(gè)類型轉(zhuǎn)換就獲取到了成員 b 也就是 *int16 的指針地址。既然拿到指針類型了,就可以修改該指針指向的變量的值了。
這種方法不要隨意使用。

不要把 uintptr 類型賦值給臨時(shí)變量

下面這段代碼看似和上面的一樣的,引入了一個(gè)臨時(shí)變量 tmp,讓把原來(lái)的一行拆成了兩行,這里的 tmp 是 uintptr 類型。這種引入 uintptr 類型的臨時(shí)變量,破壞原來(lái)整行代碼的用法是錯(cuò)誤的:

func main() {
    tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
    pb := (*int64)(unsafe.Pointer(tmp))
    *pb = 42
    fmt.Println(x.b)
}

原因很微妙。一些垃圾回收器會(huì)把內(nèi)存中變量移來(lái)移去以減少內(nèi)存碎片等問(wèn)題。這種類型的垃圾回收器稱為移動(dòng)GC。當(dāng)一個(gè)變量在內(nèi)存中移動(dòng)后,所有保存該變量舊地址的指針必須同時(shí)被更新為變量移動(dòng)后的新地址。從垃圾回收器的角度看,unsafe.Pointer 是一個(gè)變量指針,當(dāng)變量移動(dòng)后它的值也會(huì)被更新。而 uintptr 僅僅是一個(gè)數(shù)值,在垃圾回收的時(shí)候這個(gè)值是不會(huì)變的。

類似的錯(cuò)誤用法還有像下面這樣:

pT := uintptr(unsafe.Pointer(new(T))) // 提示: 錯(cuò)誤!

當(dāng)垃圾回收器將會(huì)在語(yǔ)句執(zhí)行結(jié)束后回收內(nèi)存,在這之后,pT存儲(chǔ)的是變量的舊地址,而這個(gè)時(shí)候這個(gè)地址對(duì)應(yīng)的已經(jīng)不是那個(gè)變量了。

目前Go語(yǔ)言還沒(méi)有使用移動(dòng)GC,所以上面的錯(cuò)誤用法很多時(shí)候是可以正確運(yùn)行的(運(yùn)行了幾次,都沒(méi)有出錯(cuò))。但是還是存在其他移動(dòng)變量的場(chǎng)景。
這樣的代碼能夠通過(guò)編譯并運(yùn)行,編譯器不會(huì)報(bào)錯(cuò),不過(guò)會(huì)給一個(gè)提示性的錯(cuò)誤信息:

possible misuse of unsafe.Pointer

所以還是可以在編譯的時(shí)候發(fā)現(xiàn)的。這里強(qiáng)烈建議遵守最小可用原則,不要使用任何包含變量地址的 uintptr 類型的變量,并減少不必要的 unsafe.Pointer 類型到 uintptr 類型的轉(zhuǎn)換。像本小節(jié)第一個(gè)例子里那樣,轉(zhuǎn)換為 uintptr 類型,最終在轉(zhuǎn)換回 unsafe.Pointer 類型的操作,都要在一條語(yǔ)句中完成。

reflect 包返回的 uintptr

當(dāng)調(diào)用一個(gè)庫(kù)函數(shù),并且返回的是 uintptr 類型地址時(shí),比如下面的 reflect 包中的幾個(gè)函數(shù)。這些結(jié)果應(yīng)該立刻轉(zhuǎn)換為 unsafe.Pointer 來(lái)確保它們?cè)诮酉聛?lái)代碼中能夠始終指向原來(lái)的變量:

package reflect

func (Value) Pointer() uintptr
func (Value) UnsafeAddr() uintptr
func (Value) InterfaceData() [2]uintptr // (index 1)

一般的函數(shù)盡量不要返回 uintptr 類型,可能也就反射這類底層編程的包有這種情況。
下一節(jié)的示例中會(huì)用到 reflect.UnsafeAddr 函數(shù),示例中立刻在同一行代碼中就把返回值轉(zhuǎn)成了 nsafe.Pointer 類型。

示例:深度相等

這篇要解決反射章節(jié)第一個(gè)例子 dispaly 中沒(méi)有處理的循環(huán)引用的問(wèn)題。這里需要使用 unsafe.Pointer 類型來(lái)保證地址可以始終指向最初的那個(gè)變量。

reflect 包中的 DeepEqual 函數(shù)用來(lái)報(bào)告兩個(gè)變量的值是否深度相等。DeepEqual 函數(shù)的基本類型使用內(nèi)置的 == 操作符進(jìn)行比較。對(duì)于組合類型,它逐層深入比較相應(yīng)的元素。因?yàn)檫@個(gè)函數(shù)適合于任意的一對(duì)變量值的比較,甚至是那些無(wú)法通過(guò) == 來(lái)比較的值,所以在一些測(cè)試代碼中廣泛地使用這個(gè)函數(shù)。下面的代碼就是用 DeepEqual 來(lái)比較兩個(gè) []string 類型的值:

func TestSplit(t *testing.T) {
    got := strings.Split("a:b:c", ":")
    want := []string{"a", "b", "c"}
    if !reflect.DeepEqual(got, want) { /* ... */ }
}

DeepEqual 的不足

雖然 DeepEqual 很方便,可以支持任意的數(shù)據(jù)類型,但是它的不足是判斷過(guò)于武斷。例如,一個(gè)值為 nil 的 map 和一個(gè)值不為 nil 的空 map 會(huì)判斷為不相等,一個(gè)值為 nil 的切片和不為 nil 的空切片同樣也會(huì)判斷為不相等:

var c, d map[string]int = nil, make(map[string]int)
fmt.Println(reflect.DeepEqual(c, d)) // "false"

var a, b []string = nil, []string{}
fmt.Println(reflect.DeepEqual(a, b)) // "false"

自定義比較函數(shù)

所以,接下來(lái)要自己定義一個(gè) Equal 函數(shù)。和 DeepEqual 類似,但是可以把一個(gè)值為 nil 的切片或 map 和一個(gè)值不為 nil 的空切片或 map 判斷為相等。對(duì)參數(shù)的基本遞歸檢查可以通過(guò)反射來(lái)實(shí)現(xiàn)。需要定義一個(gè)未導(dǎo)出的函數(shù) equal 用來(lái)進(jìn)行遞歸檢查,隱藏反射的細(xì)節(jié)。參數(shù) seen 是為了檢查循環(huán)引用,并且因?yàn)橐f歸所以作為參數(shù)進(jìn)行傳遞。對(duì)于每對(duì)要進(jìn)行比較的值 x 和 y,equal 函數(shù)檢查兩者是否合法(IsValid)以及它們是否具有相同的類型(Type)。函數(shù)的結(jié)果通過(guò) switch 的 case 語(yǔ)句返回,在 case 中比較兩個(gè)相同類型的值:

package equal

import (
    "reflect"
    "unsafe"
)

func equal(x, y reflect.Value, seen map[comparison]bool) bool {
    if !x.IsValid() || !y.IsValid() {
        return x.IsValid() == y.IsValid()
    }
    if x.Type() != y.Type() {
        return false
    }

    // 循環(huán)檢查
    if x.CanAddr() && y.CanAddr() {
        xptr := unsafe.Pointer(x.UnsafeAddr()) // 獲取變量的地址的數(shù)值,用于比較是不是相同的引用
        yptr := unsafe.Pointer(y.UnsafeAddr())
        if xptr == yptr {
            return true // 相同的引用
        }
        c := comparison{xptr, yptr, x.Type()}
        if seen[c] {
            return true // seen map 里已經(jīng)存在的元素,表示已經(jīng)比較過(guò)了
        }
        seen[c] = true
    }

    switch x.Kind() {
    case reflect.Bool:
        return x.Bool() == y.Bool()
    case reflect.String:
        return x.String() == y.String()

    // 各種數(shù)值類型
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
        reflect.Int64:
        return x.Int() == y.Int()
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
        reflect.Uint64, reflect.Uintptr:
        return x.Uint() == y.Uint()
    case reflect.Float32, reflect.Float64:
        return x.Float() == y.Float()
    case reflect.Complex64, reflect.Complex128:
        return x.Complex() == y.Complex()

    case reflect.Chan, reflect.UnsafePointer, reflect.Func:
        return x.Pointer() == y.Pointer()

    case reflect.Ptr, reflect.Interface:
        return equal(x.Elem(), y.Elem(), seen)

    case reflect.Array, reflect.Slice:
        if x.Len() != y.Len() {
            return false
        }
        for i := 0; i < x.Len(); i++ {
            if !equal(x.Index(i), y.Index(i), seen) {
                return false
            }
        }
        return true

    case reflect.Struct:
        for i, n := 0, x.NumField(); i < n; i++ {
            if !equal(x.Field(i), y.Field(i), seen) {
                return false
            }
        }
        return true

    case reflect.Map:
        if x.Len() != y.Len() {
            return false
        }
        for _, k := range x.MapKeys() {
            if !equal(x.MapIndex(k), y.MapIndex(k), seen) {
                return false
            }
        }
        return true
    }
    panic("unreachable")
}

// Equal 函數(shù),檢查x 和 y是否深度相等
func Equal(x, y interface{}) bool {
    seen := make(map[comparison]bool)
    return equal(reflect.ValueOf(x), reflect.ValueOf(y), seen)
}

type comparison struct {
    x, y unsafe.Pointer
    t    reflect.Type
}

在 API 中不暴露反射的細(xì)節(jié),所以最后的可導(dǎo)出的 Equel 函數(shù)對(duì)參數(shù)顯式調(diào)用 reflect.ValueOf 函數(shù)。

支持循環(huán)引用

為了確保算法終止設(shè)置可以對(duì)循環(huán)數(shù)據(jù)結(jié)果進(jìn)行比較,它必須記錄哪兩對(duì)變量已經(jīng)比較過(guò)了,并且避免再次進(jìn)行比較。Equal 函數(shù)定義了一個(gè)叫做 comparison 的結(jié)構(gòu)體集合,每個(gè)元素都包含兩個(gè)變量的地址(unsafe.Pointer 表示)以及比較的類型。比如切片的比較,x 和 x[0] 的地址是一樣的,這時(shí)候就要分開(kāi)是兩個(gè)切片的比較 x 和 y,還是切片的兩個(gè)元素的比較 x[0] 和 y[0]。
當(dāng) equal 確認(rèn)了兩個(gè)參數(shù)都是合法的并且類型也一樣,在執(zhí)行 switch 語(yǔ)句進(jìn)行比較之前,先檢查這兩個(gè)變量是否已經(jīng)比較過(guò)了,如果已經(jīng)比較過(guò)了,則直接返回結(jié)果并終止這次遞歸比較。

unsafe.Pointer
就是上一節(jié)講的問(wèn)題,reflect.UnsafeAddr 返回的是一個(gè) uintptr 類型(字母意思就是不安全的地址),這里需要直接轉(zhuǎn)成 unsafe.Pointer 類型來(lái)保證地址可以始終指向最初的那個(gè)變量。

測(cè)試驗(yàn)證

下面輸出完整的測(cè)試代碼:

package equal

import (
    "bytes"
    "fmt"
    "testing"
)

func TestEqual(t *testing.T) {
    one, oneAgain, two := 1, 1, 2

    type CyclePtr *CyclePtr
    var cyclePtr1, cyclePtr2 CyclePtr
    cyclePtr1 = &cyclePtr1
    cyclePtr2 = &cyclePtr2

    type CycleSlice []CycleSlice
    var cycleSlice = make(CycleSlice, 1)
    cycleSlice[0] = cycleSlice

    ch2, ch3 := make(chan int), make(chan int)
    var ch2ro <-chan int = ch2

    type mystring string

    var iface1, iface1Again, iface2 interface{} = &one, &oneAgain, &two

    for _, test := range []struct {
        x, y interface{}
        want bool
    }{
        // basic types
        {1, 1, true},
        {1, 2, false},   // different values
        {1, 1.0, false}, // different types
        {"foo", "foo", true},
        {"foo", "bar", false},
        {mystring("foo"), "foo", false}, // different types
        // slices
        {[]string{"foo"}, []string{"foo"}, true},
        {[]string{"foo"}, []string{"bar"}, false},
        {[]string{}, []string(nil), true},
        // slice cycles
        {cycleSlice, cycleSlice, true},
        // maps
        {
            map[string][]int{"foo": {1, 2, 3}},
            map[string][]int{"foo": {1, 2, 3}},
            true,
        },
        {
            map[string][]int{"foo": {1, 2, 3}},
            map[string][]int{"foo": {1, 2, 3, 4}},
            false,
        },
        {
            map[string][]int{},
            map[string][]int(nil),
            true,
        },
        // pointers
        {&one, &one, true},
        {&one, &two, false},
        {&one, &oneAgain, true},
        {new(bytes.Buffer), new(bytes.Buffer), true},
        // pointer cycles
        {cyclePtr1, cyclePtr1, true},
        {cyclePtr2, cyclePtr2, true},
        {cyclePtr1, cyclePtr2, true}, // they're deeply equal
        // functions
        {(func())(nil), (func())(nil), true},
        {(func())(nil), func() {}, false},
        {func() {}, func() {}, false},
        // arrays
        {[...]int{1, 2, 3}, [...]int{1, 2, 3}, true},
        {[...]int{1, 2, 3}, [...]int{1, 2, 4}, false},
        // channels
        {ch2, ch2, true},
        {ch2, ch3, false},
        {ch2ro, ch2, false}, // NOTE: not equal
        // interfaces
        {&iface1, &iface1, true},
        {&iface1, &iface2, false},
        {&iface1Again, &iface1, true},
    } {
        if Equal(test.x, test.y) != test.want {
            t.Errorf("Equal(%v, %v) = %t",
                test.x, test.y, !test.want)
        }
    }
}

func Example_equal() {
    fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3}))        // "true"
    fmt.Println(Equal([]string{"foo"}, []string{"bar"}))      // "false"
    fmt.Println(Equal([]string(nil), []string{}))             // "true"
    fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true"
    // Output:
    // true
    // false
    // true
    // true
}

func Example_equalCycle() {
    // Circular linked lists a -> b -> a and c -> c.
    type link struct {
        value string
        tail  *link
    }
    a, b, c := &link{value: "a"}, &link{value: "b"}, &link{value: "c"}
    a.tail, b.tail, c.tail = b, a, c
    fmt.Println(Equal(a, a)) // "true"
    fmt.Println(Equal(b, b)) // "true"
    fmt.Println(Equal(c, c)) // "true"
    fmt.Println(Equal(a, b)) // "false"
    fmt.Println(Equal(a, c)) // "false"
    // Output:
    // true
    // true
    // true
    // false
    // false
}

在最后的示例測(cè)試函數(shù) Example_equalCycle 中,驗(yàn)證了一個(gè)循環(huán)鏈表也能完成比較,而不會(huì)卡?。?/p>

type link struct {
    value string
    tail  *link
}
a, b, c := &link{value: "a"}, &link{value: "b"}, &link{value: "c"}
a.tail, b.tail, c.tail = b, a, c

關(guān)于安全的注意事項(xiàng)

高級(jí)語(yǔ)言將程序、程序員和神秘的機(jī)器指令集隔離開(kāi)來(lái),并且也隔離了諸如變量在內(nèi)存中的存儲(chǔ)位置,數(shù)據(jù)類型的大小,數(shù)據(jù)結(jié)構(gòu)的內(nèi)存布局,以及關(guān)于機(jī)器的其他實(shí)現(xiàn)細(xì)節(jié)。因?yàn)橛羞@個(gè)隔離層的存在,我們可以編寫(xiě)安全健壯的代碼并且不加改動(dòng)就可以在任何操作系統(tǒng)上運(yùn)行。
但 unsafe 包可以讓程序穿透這層隔離去使用一些關(guān)鍵的但通過(guò)其他方式無(wú)法使用到的特性,或者是為了實(shí)現(xiàn)更高的性能。付出的代價(jià)通常就是程序的可移植性和安全性,所以當(dāng)你使用 unsafe 的時(shí)候就得自己承擔(dān)風(fēng)險(xiǎn)。大多數(shù)情況都不需要甚至永遠(yuǎn)不需要使用 unsafe 包。當(dāng)然,偶爾還是會(huì)遇到一些使用的場(chǎng)景,其中一些關(guān)鍵代碼最好還是通過(guò) unsafe 來(lái)寫(xiě)。如果用了,那就要確保盡可能地限制在小范圍內(nèi)使用,這樣大多數(shù)的程序就不會(huì)受到這個(gè)影響。

向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 un ns
AI