包 unsafe 廣泛使用在和操作系統(tǒng)交互的低級(jí)包中, 例如 runtime、os、syscall、net 等,但是普通程序是不需要使用它的。
創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),扎魯特旗企業(yè)網(wǎng)站建設(shè),扎魯特旗品牌網(wǎng)站建設(shè),網(wǎng)站定制,扎魯特旗網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷(xiāo),網(wǎng)絡(luò)優(yōu)化,扎魯特旗網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M(mǎn)足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專(zhuān)業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶(hù)成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
函數(shù) unsafe.Sizeof 報(bào)告?zhèn)鬟f給它的參數(shù)在內(nèi)存中占用的字節(jié)(Byte)長(zhǎng)度(1Byte=8bit,1個(gè)字節(jié)是8位),參數(shù)可以是任意類(lèi)型的表達(dá)式,但它不會(huì)對(duì)表達(dá)式進(jìn)行求值。對(duì) Sizeof 的調(diào)用會(huì)返回一個(gè) uintptr 類(lèi)型的常量表達(dá)式,所以返回的結(jié)果可以作為數(shù)組類(lèi)型的長(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ǔ)言中非聚合類(lèi)型通常有一個(gè)固定的大小,盡管在不同工具鏈下生成的實(shí)際大小可能會(huì)有所不同??紤]到可移植性,引用類(lèi)型或包含引用類(lèi)型的大小都是1個(gè)字(word),轉(zhuǎn)換為字節(jié)數(shù),在32位系統(tǒng)上是4個(gè)字節(jié),在64位系統(tǒng)上是8個(gè)字節(jié)。
類(lèi)型 | 大小 |
---|---|
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) |
在類(lèi)型的值在內(nèi)存中對(duì)齊的情況下,計(jì)算機(jī)的加載或者寫(xiě)入會(huì)很高效。例如,int16的大小是2字節(jié)地址應(yīng)該是偶數(shù),rune類(lèi)型的大小是4字節(jié)地址應(yīng)該是4的倍數(shù),float64、uint64 或 64位指針的大小是8字節(jié)地址應(yīng)該是8的倍數(shù)。對(duì)于更大倍數(shù)的地址對(duì)齊是不需要的,即使是complex128等較大的數(shù)據(jù)類(lèi)型最多也只是8字節(jié)對(duì)齊。
結(jié)構(gòu)體的內(nèi)存對(duì)齊
因此,聚合類(lèi)型(結(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)體成員的類(lèi)型是不同的,不同的排列順序可能使得結(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è)容易掌握的建議是,將相同類(lèi)型的成員定義在一起有可能更節(jié)約內(nèi)存空間。
函數(shù) unsafe.Alignof 報(bào)告它參數(shù)類(lèi)型所要求的對(duì)齊方式。和 Sizeof 一樣,它的參數(shù)可以是任意類(lèi)型的表達(dá)式,并且返回一個(gè)常量。通常情況下布爾和數(shù)值類(lèi)型對(duì)齊到它們的長(zhǎng)度(最多8個(gè)字節(jié)), 其它的類(lèi)型則按字(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)存布局很有幫助。
很多指針類(lèi)型都寫(xiě)做 *T,意思是“一個(gè)指向T類(lèi)型變量的指針”。unsafe.Pointer 類(lèi)型是一種特殊類(lèi)型的指針,它可以存儲(chǔ)任何變量的地址。這里不可以直接通過(guò) *P 來(lái)獲取 unsafe.Pointer 指針指向的那個(gè)變量的值,因?yàn)椴⒉恢雷兞康木唧w類(lèi)型。和普通的指針一樣,unsafe.Pointer 類(lèi)型的指針是可比較的并且可以和 nil 做比較,nil 是指針類(lèi)型的零值。
一個(gè)普通的指針 *T 可以轉(zhuǎn)換為 unsafe.Pointer 類(lèi)型的指針,并且一個(gè) unsafe.Pointer 類(lèi)型的指針也可以轉(zhuǎn)換回普通的指針,被轉(zhuǎn)換回普通指針的類(lèi)型不需要和原來(lái)的 *T 類(lèi)型相同。這里有一個(gè)簡(jiǎn)單的應(yīng)用場(chǎng)景,先將 *float64 類(lèi)型指針轉(zhuǎn)化為 *uint64 然后再把內(nèi)存中的值打印出來(lái)。這時(shí)候就是按照 uint64 類(lèi)型來(lái)把值打印出來(lái),這樣就可以看到浮點(diǎn)類(lèi)型的變量在內(nèi)存中的位模式:
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
func main() {
fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
}
很多 unsafe.Pointer 類(lèi)型的值都是從普通指針到原始內(nèi)存地址以及再?gòu)膬?nèi)存地址到普通指針進(jìn)行轉(zhuǎn)換的中間值。下面的例子獲取變量 x 的地址,然后加上其成員 b 的地址偏移量,并將結(jié)果轉(zhuǎn)換為 *int16 指針類(lèi)型,接著通過(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ù)類(lèi)型,所以直接用一個(gè)類(lèi)型轉(zhuǎn)換就獲取到了成員 b 也就是 *int16 的指針地址。既然拿到指針類(lèi)型了,就可以修改該指針指向的變量的值了。
這種方法不要隨意使用。
下面這段代碼看似和上面的一樣的,引入了一個(gè)臨時(shí)變量 tmp,讓把原來(lái)的一行拆成了兩行,這里的 tmp 是 uintptr 類(lèi)型。這種引入 uintptr 類(lèi)型的臨時(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)題。這種類(lèi)型的垃圾回收器稱(chēng)為移動(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ì)變的。
類(lèi)似的錯(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 類(lèi)型的變量,并減少不必要的 unsafe.Pointer 類(lèi)型到 uintptr 類(lèi)型的轉(zhuǎn)換。像本小節(jié)第一個(gè)例子里那樣,轉(zhuǎn)換為 uintptr 類(lèi)型,最終在轉(zhuǎn)換回 unsafe.Pointer 類(lèi)型的操作,都要在一條語(yǔ)句中完成。
當(dāng)調(diào)用一個(gè)庫(kù)函數(shù),并且返回的是 uintptr 類(lèi)型地址時(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 類(lèi)型,可能也就反射這類(lèi)底層編程的包有這種情況。
下一節(jié)的示例中會(huì)用到 reflect.UnsafeAddr 函數(shù),示例中立刻在同一行代碼中就把返回值轉(zhuǎn)成了 nsafe.Pointer 類(lèi)型。
這篇要解決反射章節(jié)第一個(gè)例子 dispaly 中沒(méi)有處理的循環(huán)引用的問(wèn)題。這里需要使用 unsafe.Pointer 類(lèi)型來(lái)保證地址可以始終指向最初的那個(gè)變量。
reflect 包中的 DeepEqual 函數(shù)用來(lái)報(bào)告兩個(gè)變量的值是否深度相等。DeepEqual 函數(shù)的基本類(lèi)型使用內(nèi)置的 == 操作符進(jìn)行比較。對(duì)于組合類(lèi)型,它逐層深入比較相應(yīng)的元素。因?yàn)檫@個(gè)函數(shù)適合于任意的一對(duì)變量值的比較,甚至是那些無(wú)法通過(guò) == 來(lái)比較的值,所以在一些測(cè)試代碼中廣泛地使用這個(gè)函數(shù)。下面的代碼就是用 DeepEqual 來(lái)比較兩個(gè) []string 類(lèi)型的值:
func TestSplit(t *testing.T) {
got := strings.Split("a:b:c", ":")
want := []string{"a", "b", "c"}
if !reflect.DeepEqual(got, want) { /* ... */ }
}
雖然 DeepEqual 很方便,可以支持任意的數(shù)據(jù)類(lèi)型,但是它的不足是判斷過(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"
所以,接下來(lái)要自己定義一個(gè) Equal 函數(shù)。和 DeepEqual 類(lèi)似,但是可以把一個(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)以及它們是否具有相同的類(lèi)型(Type)。函數(shù)的結(jié)果通過(guò) switch 的 case 語(yǔ)句返回,在 case 中比較兩個(gè)相同類(lèi)型的值:
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ù)值類(lèi)型
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ù)。
為了確保算法終止設(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 表示)以及比較的類(lèi)型。比如切片的比較,x 和 x[0] 的地址是一樣的,這時(shí)候就要分開(kāi)是兩個(gè)切片的比較 x 和 y,還是切片的兩個(gè)元素的比較 x[0] 和 y[0]。
當(dāng) equal 確認(rèn)了兩個(gè)參數(shù)都是合法的并且類(lèi)型也一樣,在執(zhí)行 switch 語(yǔ)句進(jìn)行比較之前,先檢查這兩個(gè)變量是否已經(jīng)比較過(guò)了,如果已經(jīng)比較過(guò)了,則直接返回結(jié)果并終止這次遞歸比較。
unsafe.Pointer
就是上一節(jié)講的問(wèn)題,reflect.UnsafeAddr 返回的是一個(gè) uintptr 類(lèi)型(字母意思就是不安全的地址),這里需要直接轉(zhuǎn)成 unsafe.Pointer 類(lèi)型來(lái)保證地址可以始終指向最初的那個(gè)變量。
下面輸出完整的測(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ì)卡住:
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
高級(jí)語(yǔ)言將程序、程序員和神秘的機(jī)器指令集隔離開(kāi)來(lái),并且也隔離了諸如變量在內(nèi)存中的存儲(chǔ)位置,數(shù)據(jù)類(lèi)型的大小,數(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è)影響。